Browse Source

Merge with master

pull/1166/head
Andrew Shvayka 8 years ago
parent
commit
8c9a6c3e47
  1. 110
      application/src/main/data/upgrade/2.1.2/schema_update.cql
  2. 32
      application/src/main/data/upgrade/2.1.2/schema_update.sql
  3. 14
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  4. 33
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  5. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  6. 49
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  7. 9
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  8. 34
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  10. 97
      application/src/main/resources/thingsboard.yml
  11. 104
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  12. 32
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  13. 66
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java
  14. 1
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  15. 1
      common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
  16. 69
      dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java
  17. 32
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java
  18. 7
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  19. 54
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  20. 7
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  21. 7
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java
  22. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java
  23. 41
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
  24. 42
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  25. 23
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  26. 49
      dao/src/main/resources/cassandra/schema-entities.cql
  27. 240
      dao/src/main/resources/cassandra/system-data.cql
  28. 3
      dao/src/main/resources/sql/schema-entities.sql
  29. 2
      dao/src/test/resources/sql/drop-all-tables.sql
  30. 5
      msa/docker/.env
  31. 16
      msa/docker/docker-compose.yml
  32. 2
      msa/docker/docker-start-services.sh
  33. 1
      msa/docker/docker-update-service.sh
  34. 3
      msa/docker/haproxy/config/haproxy.cfg
  35. 2
      msa/docker/tb-node/conf/thingsboard.conf
  36. 2
      msa/js-executor/docker/Dockerfile
  37. 3
      msa/js-executor/pom.xml
  38. 2
      msa/js-executor/src/main/scripts/control/deb/postinst
  39. 3
      msa/tb-node/pom.xml
  40. 2
      msa/web-ui/docker/Dockerfile
  41. 3
      msa/web-ui/pom.xml
  42. 2
      msa/web-ui/src/main/scripts/control/deb/postinst
  43. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
  44. 2
      transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  45. 2
      transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  46. 4
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java
  47. 7
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  48. 17
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
  49. 11
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  50. 2
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
  51. 2
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
  52. 21
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java
  53. 55
      transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttTopicMatcher.java
  54. 53
      transport/mqtt-transport/src/main/resources/tb-mqtt-transport.yml
  55. 33
      ui/src/app/api/entity.service.js
  56. 8
      ui/src/app/api/subscription.js
  57. 53
      ui/src/app/api/time.service.js
  58. 3
      ui/src/app/api/user.service.js
  59. 8
      ui/src/app/common/types.constant.js
  60. 10
      ui/src/app/components/timewindow-panel.controller.js
  61. 15
      ui/src/app/components/timewindow-panel.tpl.html
  62. 2
      ui/src/app/components/timewindow.directive.js
  63. 22
      ui/src/app/entity-view/entity-view-fieldset.tpl.html
  64. 4
      ui/src/app/entity-view/entity-view.routes.js
  65. 22
      ui/src/app/entity/entity-filter-view.directive.js
  66. 8
      ui/src/app/entity/entity-filter.directive.js
  67. 83
      ui/src/app/entity/entity-filter.tpl.html
  68. 11
      ui/src/app/entity/entity-subtype-autocomplete.directive.js
  69. 10
      ui/src/app/entity/entity-subtype-list.directive.js
  70. 11
      ui/src/app/entity/entity-subtype-select.directive.js
  71. 1
      ui/src/app/help/help-links.constant.js
  72. 1545
      ui/src/app/locale/locale.constant-TR.json
  73. 11
      ui/src/app/locale/locale.constant-en_US.json
  74. 2107
      ui/src/app/locale/locale.constant-es_ES.json
  75. 2919
      ui/src/app/locale/locale.constant-fr_FR.json
  76. 89
      ui/src/app/locale/locale.constant-it_IT.json
  77. 3
      ui/src/app/locale/locale.constant-ja_JA.json
  78. 3
      ui/src/app/locale/locale.constant-ko_KR.json
  79. 3
      ui/src/app/locale/locale.constant-ru_RU.json
  80. 3
      ui/src/app/locale/locale.constant-zh_CN.json
  81. 8
      ui/src/app/services/menu.service.js
  82. 2
      ui/src/app/widget/lib/google-map.js

110
application/src/main/data/upgrade/2.1.2/schema_update.cql

@ -0,0 +1,110 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
DROP TABLE IF EXISTS thingsboard.entity_views;
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);

32
application/src/main/data/upgrade/2.1.2/schema_update.sql

@ -0,0 +1,32 @@
--
-- Copyright © 2016-2018 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP TABLE IF EXISTS entity_views;
CREATE TABLE IF NOT EXISTS entity_view (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
type varchar(255),
name varchar(255),
keys varchar(255),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

14
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.controller;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -49,6 +51,11 @@ public class DashboardController extends BaseController {
public static final String DASHBOARD_ID = "dashboardId";
@Value("${dashboard.max_datapoints_limit}")
@Getter
private long maxDatapointsLimit;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
@ResponseBody
@ -56,6 +63,13 @@ public class DashboardController extends BaseController {
return System.currentTimeMillis();
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET)
@ResponseBody
public long getMaxDatapointsLimit() throws ThingsboardException {
return maxDatapointsLimit;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody

33
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -26,6 +27,7 @@ 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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType;
@ -38,6 +40,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.List;
import java.util.stream.Collectors;
@ -161,6 +164,7 @@ public class EntityViewController extends BaseController {
public TextPageData<EntityView> getCustomerEntityViews(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@ -170,7 +174,11 @@ public class EntityViewController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
if (type != null && type.trim().length() > 0) {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type));
} else {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
@ -181,13 +189,19 @@ public class EntityViewController extends BaseController {
@ResponseBody
public TextPageData<EntityView> getTenantEntityViews(
@RequestParam int limit,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
if (type != null && type.trim().length() > 0) {
return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type));
} else {
return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}
@ -199,6 +213,7 @@ public class EntityViewController extends BaseController {
public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException {
checkNotNull(query);
checkNotNull(query.getParameters());
checkNotNull(query.getEntityViewTypes());
checkEntityId(query.getParameters().getEntityId());
try {
List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get());
@ -215,4 +230,18 @@ public class EntityViewController extends BaseController {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/types", method = RequestMethod.GET)
@ResponseBody
public List<EntitySubtype> getEntityViewTypes() throws ThingsboardException {
try {
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
ListenableFuture<List<EntitySubtype>> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId);
return checkNotNull(entityViewTypes.get());
} catch (Exception e) {
throw handleException(e);
}
}
}

5
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -97,6 +97,11 @@ public class ThingsboardInstallService {
databaseUpgradeService.upgradeDatabase("2.0.0");
case "2.1.1":
log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
databaseUpgradeService.upgradeDatabase("2.1.1");
log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts");

49
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -39,10 +39,19 @@ import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATIO
import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
import static org.thingsboard.server.service.install.DatabaseHelper.DEVICE;
import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
import static org.thingsboard.server.service.install.DatabaseHelper.ID;
import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@NoSqlDao
@ -203,6 +212,46 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
break;
case "2.0.0":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.1", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
break;
case "2.1.1":
log.info("Upgrading Cassandra DataBase from version {} to 2.1.2 ...", fromVersion);
cluster.getSession();
ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
log.info("Dumping entity views ...");
Path entityViewsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), ENTITY_VIEWS,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
new String[]{"", "", "", "", "", "", "default", "", "0", "0", "", ""},
"tb-entity-views");
log.info("Entity views dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
log.info("Restoring entity views ...");
if (entityViewsDump != null) {
CassandraDbHelper.loadCf(ks, cluster.getSession(), ENTITY_VIEW,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, NAME, TYPE, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump);
Files.deleteIfExists(entityViewsDump);
}
log.info("Entity views restored.");
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
}

9
application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java

@ -45,14 +45,23 @@ public class DatabaseHelper {
public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
public static final String DEVICE = "device";
public static final String ENTITY_ID = "entity_id";
public static final String TENANT_ID = "tenant_id";
public static final String ENTITY_TYPE = "entity_type";
public static final String CUSTOMER_ID = "customer_id";
public static final String SEARCH_TEXT = "search_text";
public static final String ADDITIONAL_INFO = "additional_info";
public static final String ASSET = "asset";
public static final String DASHBOARD = "dashboard";
public static final String ENTITY_VIEWS = "entity_views";
public static final String ENTITY_VIEW = "entity_view";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String NAME = "name";
public static final String KEYS = "keys";
public static final String START_TS = "start_ts";
public static final String END_TS = "end_ts";
public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
public static final String CONFIGURATION = "configuration";

34
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -31,14 +31,24 @@ import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
import static org.thingsboard.server.service.install.DatabaseHelper.CUSTOMER_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.DASHBOARD;
import static org.thingsboard.server.service.install.DatabaseHelper.END_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_TYPE;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEW;
import static org.thingsboard.server.service.install.DatabaseHelper.ENTITY_VIEWS;
import static org.thingsboard.server.service.install.DatabaseHelper.ID;
import static org.thingsboard.server.service.install.DatabaseHelper.KEYS;
import static org.thingsboard.server.service.install.DatabaseHelper.NAME;
import static org.thingsboard.server.service.install.DatabaseHelper.SEARCH_TEXT;
import static org.thingsboard.server.service.install.DatabaseHelper.START_TS;
import static org.thingsboard.server.service.install.DatabaseHelper.TENANT_ID;
import static org.thingsboard.server.service.install.DatabaseHelper.TITLE;
import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@Service
@Profile("install")
@ -115,6 +125,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
}
break;
case "2.1.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Dumping entity views ...");
Path entityViewsDump = SqlDbHelper.dumpTableIfExists(conn, ENTITY_VIEWS,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO},
new String[]{"", "", "", "", "", "default", "", "", "0", "0", "", ""},
"tb-entity-views", true);
log.info("Entity views dumped.");
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.1.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
log.info("Restoring entity views ...");
if (entityViewsDump != null) {
SqlDbHelper.loadTable(conn, ENTITY_VIEW,
new String[]{ID, ENTITY_ID, ENTITY_TYPE, TENANT_ID, CUSTOMER_ID, TYPE, NAME, KEYS, START_TS, END_TS, SEARCH_TEXT, ADDITIONAL_INFO}, entityViewsDump, true);
Files.deleteIfExists(entityViewsDump);
}
log.info("Entity views restored.");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);

4
application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java

@ -147,6 +147,8 @@ public class CassandraDbHelper {
str = new Double(row.getDouble(index)).toString();
} else if (type == DataType.cint()) {
str = new Integer(row.getInt(index)).toString();
} else if (type == DataType.bigint()) {
str = new Long(row.getLong(index)).toString();
} else if (type == DataType.uuid()) {
str = row.getUUID(index).toString();
} else if (type == DataType.timeuuid()) {
@ -193,6 +195,8 @@ public class CassandraDbHelper {
boundStatement.setDouble(column, Double.valueOf(value));
} else if (type == DataType.cint()) {
boundStatement.setInt(column, Integer.valueOf(value));
} else if (type == DataType.bigint()) {
boundStatement.setLong(column, Long.valueOf(value));
} else if (type == DataType.uuid()) {
boundStatement.setUUID(column, UUID.fromString(value));
} else if (type == DataType.timeuuid()) {

97
application/src/main/resources/thingsboard.yml

@ -77,46 +77,10 @@ security:
# Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}"
# Device communication protocol parameters
http:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
# MQTT server parameters
mqtt:
# Enable/disable mqtt transport protocol.
enabled: "${MQTT_ENABLED:false}"
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}"
adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
timeout: "${MQTT_TIMEOUT:10000}"
netty:
leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
# MQTT SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${MQTT_SSL_ENABLED:false}"
# SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
# Path to the key store that holds the SSL certificate
key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
# Password used to access the key store
key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
# CoAP server parameters
coap:
# Enable/disable coap transport protocol.
enabled: "${COAP_ENABLED:false}"
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
timeout: "${COAP_TIMEOUT:10000}"
# Dashboard parameters
dashboard:
# Maximum allowed datapoints fetched by widgets
max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}"
#Quota parameters
quota:
@ -136,8 +100,8 @@ quota:
# Array of blacklist hosts
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
topSize: "${QUOTA_HOST_LOG_TOP_SIZE:10}"
intervalMin: "${QUOTA_HOST_LOG_INTERVAL_MIN:2}"
rule:
tenant:
# Max allowed number of API requests in interval for single tenant
@ -153,10 +117,10 @@ quota:
# Array of whitelist tenants
whitelist: "${QUOTA_TENANT_WHITELIST:}"
# Array of blacklist tenants
blacklist: "${QUOTA_HOST_BLACKLIST:}"
blacklist: "${QUOTA_HOST_TENANT_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
topSize: "${QUOTA_TENANT_LOG_TOP_SIZE:10}"
intervalMin: "${QUOTA_TENANT_LOG_INTERVAL_MIN:2}"
database:
entities:
@ -185,7 +149,8 @@ cassandra:
init_timeout_ms: "${CASSANDRA_CLUSTER_INIT_TIMEOUT_MS:300000}"
# Specify cassandra claster initialization retry interval (if no hosts available during startup)
init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}"
max_requests_per_connection_local: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_LOCAL:32768}"
max_requests_per_connection_remote: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_REMOTE:32768}"
# Credential parameters #
credentials: "${CASSANDRA_USE_CREDENTIALS:false}"
# Specify your username
@ -466,4 +431,42 @@ transport:
poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}"
auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}"
notifications:
topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
# Local HTTP transport parameters
http:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
# Local MQTT transport parameters
mqtt:
# Enable/disable mqtt transport protocol.
enabled: "${MQTT_ENABLED:false}"
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}"
adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
timeout: "${MQTT_TIMEOUT:10000}"
netty:
leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
# MQTT SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${MQTT_SSL_ENABLED:false}"
# SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
# Path to the key store that holds the SSL certificate
key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
# Password used to access the key store
key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
# Local CoAP transport parameters
coap:
# Enable/disable coap transport protocol.
enabled: "${COAP_ENABLED:false}"
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
timeout: "${COAP_TIMEOUT:10000}"

104
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -85,11 +85,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
testDevice = doPost("/api/device", device, Device.class);
telemetry = new TelemetryEntityView(
Arrays.asList("109", "209", "309"),
Arrays.asList("tsKey1", "tsKey2", "tsKey3"),
new AttributesEntityView(
Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"),
Arrays.asList("saValue1", "saValue2", "saValue3", "saValue4"),
Arrays.asList("shValue1", "shValue2", "shValue3", "shValue4")));
Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"),
Arrays.asList("saKey1", "saKey2", "saKey3", "saKey4"),
Arrays.asList("shKey1", "shKey2", "shKey3", "shKey4")));
}
@After
@ -144,7 +144,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test
public void testSaveEntityViewWithEmptyName() throws Exception {
doPost("/api/entityView", new EntityView())
EntityView entityView = new EntityView();
entityView.setType("default");
doPost("/api/entityView", entityView)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Entity view name should be specified!")));
}
@ -322,10 +324,10 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test
public void testTheCopyOfAttrsIntoTSForTheView() throws Exception {
Set<String> actualAttributesSet =
getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}");
Set<String> expectedActualAttributesSet =
new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
Thread.sleep(1000);
@ -333,18 +335,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
List<Map<String, Object>> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class);
assertEquals("value1", getValue(values, "caValue1"));
assertEquals(true, getValue(values, "caValue2"));
assertEquals(42.0, getValue(values, "caValue3"));
assertEquals(73, getValue(values, "caValue4"));
assertEquals("value1", getValue(values, "caKey1"));
assertEquals(true, getValue(values, "caKey2"));
assertEquals(42.0, getValue(values, "caKey3"));
assertEquals(73, getValue(values, "caKey4"));
}
@Test
public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
Set<String> actualAttributesSet =
getAttributesByKeys("{\"caValue1\":\"value1\", \"caValue2\":true, \"caValue3\":42.0, \"caValue4\":73}");
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}");
Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caValue1", "caValue2", "caValue3", "caValue4"));
Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
Thread.sleep(1000);
@ -355,6 +357,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId());
view.setName("Test entity view");
view.setType("default");
view.setKeys(telemetry);
view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10);
view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
@ -365,6 +368,69 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertEquals(0, values.size());
}
@Test
public void testGetTelemetryWhenEntityViewTimeRangeInsideTimestampRange() throws Exception {
uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}");
Thread.sleep(1000);
long startTimeMs = System.currentTimeMillis();
uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}");
Thread.sleep(1000);
uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}");
long endTimeMs = System.currentTimeMillis();
uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}");
String deviceId = testDevice.getId().getId().toString();
Set<String> keys = getTelemetryKeys("DEVICE", deviceId);
Thread.sleep(1000);
EntityView view = createEntityView("Test entity view", startTimeMs, endTimeMs);
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
String entityViewId = savedView.getId().getId().toString();
Map<String, List<Map<String, String>>> expectedValues = getTelemetryValues("DEVICE", deviceId, keys, 0L, (startTimeMs + endTimeMs) / 2);
Assert.assertEquals(2, expectedValues.get("tsKey1").size());
Assert.assertEquals(2, expectedValues.get("tsKey2").size());
Assert.assertEquals(2, expectedValues.get("tsKey3").size());
Map<String, List<Map<String, String>>> actualValues = getTelemetryValues("ENTITY_VIEW", entityViewId, keys, 0L, (startTimeMs + endTimeMs) / 2);
Assert.assertEquals(1, actualValues.get("tsKey1").size());
Assert.assertEquals(1, actualValues.get("tsKey2").size());
Assert.assertEquals(1, actualValues.get("tsKey3").size());
}
private void uploadTelemetry(String strKvs) throws Exception {
String viewDeviceId = testDevice.getId().getId().toString();
DeviceCredentials deviceCredentials =
doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options);
Thread.sleep(3000);
MqttMessage message = new MqttMessage();
message.setPayload(strKvs.getBytes());
client.publish("v1/devices/me/telemetry", message);
Thread.sleep(1000);
}
private Set<String> getTelemetryKeys(String type, String id) throws Exception {
return new HashSet<>(doGetAsync("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", List.class));
}
private Map<String, List<Map<String, String>>> getTelemetryValues(String type, String id, Set<String> keys, Long startTs, Long endTs) throws Exception {
return doGetAsync("/api/plugins/telemetry/" + type + "/" + id +
"/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, Map.class);
}
private Set<String> getAttributesByKeys(String stringKV) throws Exception {
String viewDeviceId = testDevice.getId().getId().toString();
DeviceCredentials deviceCredentials =
@ -387,7 +453,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
client.publish("v1/devices/me/attributes", message);
Thread.sleep(1000);
return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class));
return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class));
}
private Object getValue(List<Map<String, Object>> values, String stringValue) {
@ -398,12 +464,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
}
private EntityView getNewSavedEntityView(String name) throws Exception {
EntityView view = createEntityView(name, 0, 0);
return doPost("/api/entityView", view, EntityView.class);
}
private EntityView createEntityView(String name, long startTimeMs, long endTimeMs) {
EntityView view = new EntityView();
view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId());
view.setName(name);
view.setType("default");
view.setKeys(telemetry);
return doPost("/api/entityView", view, EntityView.class);
view.setStartTimeMs(startTimeMs);
view.setEndTimeMs(endTimeMs);
return view;
}
private Customer getNewCustomer(String title) {

32
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.mqtt.rpc;
import com.datastax.driver.core.utils.UUIDs;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
@ -23,19 +24,19 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
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.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest;
import org.thingsboard.server.service.security.AccessValidator;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -101,13 +102,19 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
client.subscribe("v1/devices/me/rpc/request/+", 1);
client.setCallback(new TestMqttCallback(client));
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.subscribe("v1/devices/me/rpc/request/+", MqttQoS.AT_MOST_ONCE.value());
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result));
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
@Test
@ -156,7 +163,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
client.subscribe("v1/devices/me/rpc/request/+", 1);
client.setCallback(new TestMqttCallback(client));
client.setCallback(new TestMqttCallback(client, new CountDownLatch(1)));
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
@ -204,9 +211,16 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
private static class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private Integer qoS;
TestMqttCallback(MqttAsyncClient client) {
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
@ -219,7 +233,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
MqttMessage message = new MqttMessage();
String responseTopic = requestTopic.replace("request", "response");
message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes("UTF-8"));
qoS = mqttMessage.getQos();
client.publish(responseTopic, message);
latch.countDown();
}
@Override

66
application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java

@ -15,10 +15,9 @@
*/
package org.thingsboard.server.mqtt.telemetry;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.*;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -30,9 +29,12 @@ import org.thingsboard.server.dao.service.DaoNoSqlTest;
import java.net.URI;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
@ -94,4 +96,62 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
assertEquals("3.0", values.get("key3").get(0).get("value"));
assertEquals("4", values.get("key4").get(0).get("value"));
}
@Test
public void testMqttQoSLevel() throws Exception {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion(3000);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
String payload = "{\"key\":\"value\"}";
String result = doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
assertEquals(payload, callback.getPayload());
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
private static class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private Integer qoS;
private String payload;
String getPayload() {
return payload;
}
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
payload = new String(mqttMessage.getPayload());
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -40,6 +40,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
private TenantId tenantId;
private CustomerId customerId;
private String name;
private String type;
private TelemetryEntityView keys;
private long startTimeMs;
private long endTimeMs;

1
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java

@ -30,6 +30,7 @@ public class EntityViewSearchQuery {
private RelationsSearchParameters parameters;
private String relationType;
private List<String> entityViewTypes;
public EntityRelationsQuery toEntitySearchQuery() {
EntityRelationsQuery query = new EntityRelationsQuery();

69
dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java

@ -15,8 +15,13 @@
*/
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;
@ -30,6 +35,8 @@ 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;
@ -39,14 +46,21 @@ 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_COLUMN_FAMILY_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;
/**
@ -82,13 +96,25 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
public List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<EntityViewEntity> entityViewEntities =
findPageWithTextSearch(ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
findPageWithTextSearch(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<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
List<EntityViewEntity> entityViewEntities =
findPageWithTextSearch(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<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where();
@ -110,6 +136,19 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
return DaoUtil.convertDataList(entityViewEntities);
}
@Override
public List<EntityView> 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<EntityViewEntity> entityViewEntities = findPageWithTextSearch(
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<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId);
@ -118,4 +157,30 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit
query.and(eq(ENTITY_ID_COLUMN, entityId));
return findListByStatementAsync(query);
}
@Override
public ListenableFuture<List<EntitySubtype>> 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(query);
return Futures.transform(resultSetFuture, new Function<ResultSet, List<EntitySubtype>>() {
@Nullable
@Override
public List<EntitySubtype> apply(@Nullable ResultSet resultSet) {
Result<EntitySubtypeEntity> result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet);
if (result != null) {
List<EntitySubtype> entitySubtypes = new ArrayList<>();
result.all().forEach((entitySubtypeEntity) ->
entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
);
return entitySubtypes;
} else {
return Collections.emptyList();
}
}
});
}
}

32
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java

@ -17,6 +17,7 @@ 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.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
@ -47,6 +48,16 @@ public interface EntityViewDao extends Dao<EntityView> {
*/
List<EntityView> findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find entity views 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 objects
*/
List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
/**
* Find entity views by tenantId and entity view name.
*
@ -68,6 +79,27 @@ public interface EntityViewDao extends Dao<EntityView> {
UUID customerId,
TextPageLink pageLink);
/**
* Find entity views 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 objects
*/
List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId,
UUID customerId,
String type,
TextPageLink pageLink);
ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId);
/**
* Find tenants entity view types.
*
* @return the list of tenant entity view type objects
*/
ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId);
}

7
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -16,6 +16,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.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
@ -44,8 +45,12 @@ public interface EntityViewService {
TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type);
TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type);
ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query);
ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId);
@ -55,4 +60,6 @@ public interface EntityViewService {
void deleteEntityView(EntityViewId entityViewId);
void deleteEntityViewsByTenantId(TenantId tenantId);
ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId);
}

54
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.entityview;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -29,6 +30,8 @@ import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
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.EntityView;
import org.thingsboard.server.common.data.Tenant;
@ -54,6 +57,8 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
@ -63,6 +68,7 @@ import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
/**
* Created by Victor Basanets on 8/28/2017.
@ -157,6 +163,16 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return new TextPageData<>(entityViews, pageLink);
}
@Override
public TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink 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);
validateString(type, "Incorrect type " + type);
List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink);
return new TextPageData<>(entityViews, pageLink);
}
@Override
public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId,
TextPageLink pageLink) {
@ -170,6 +186,19 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return new TextPageData<>(entityViews, pageLink);
}
@Override
public TextPageData<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink 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);
validateString(type, "Incorrect type " + type);
List<EntityView> entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId.getId(),
customerId.getId(), type, pageLink);
return new TextPageData<>(entityViews, pageLink);
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query) {
ListenableFuture<List<EntityRelation>> relations = relationService.findByQuery(query.toEntitySearchQuery());
@ -184,6 +213,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
}
return Futures.successfulAsList(futures);
});
entityViews = Futures.transform(entityViews, new Function<List<EntityView>, List<EntityView>>() {
@Nullable
@Override
public List<EntityView> apply(@Nullable List<EntityView> entityViewList) {
return entityViewList == null ? Collections.emptyList() : entityViewList.stream().filter(entityView -> query.getEntityViewTypes().contains(entityView.getType())).collect(Collectors.toList());
}
});
return entityViews;
}
@ -216,6 +254,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
public void onSuccess(@Nullable List<EntityView> result) {
cache.putIfAbsent(tenantIdAndEntityId, result);
}
@Override
public void onFailure(Throwable t) {
log.error("Error while finding entity views by tenantId and entityId", t);
@ -243,6 +282,18 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
tenantEntityViewRemover.removeEntities(tenantId);
}
@Override
public ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId) {
log.trace("Executing findEntityViewTypesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
ListenableFuture<List<EntitySubtype>> tenantEntityViewTypes = entityViewDao.findTenantEntityViewTypesAsync(tenantId.getId());
return Futures.transform(tenantEntityViewTypes,
entityViewTypes -> {
entityViewTypes.sort(Comparator.comparing(EntitySubtype::getType));
return entityViewTypes;
});
}
private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys) {
if (keys != null && !keys.isEmpty()) {
ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys);
@ -296,6 +347,9 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
@Override
protected void validateDataImpl(EntityView entityView) {
if (StringUtils.isEmpty(entityView.getType())) {
throw new DataValidationException("Entity View type should be specified!");
}
if (StringUtils.isEmpty(entityView.getName())) {
throw new DataValidationException("Entity view name should be specified!");
}

7
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -145,18 +145,21 @@ public class ModelConstants {
/**
* Cassandra entityView constants.
*/
public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_views";
public static final String ENTITY_VIEW_TABLE_FAMILY_NAME = "entity_view";
public static final String ENTITY_VIEW_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN;
public static final String ENTITY_VIEW_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ENTITY_VIEW_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ENTITY_VIEW_NAME_PROPERTY = DEVICE_NAME_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF = "entity_view_by_tenant_and_customer";
public static final String ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF = "entity_view_by_tenant_and_customer_and_type";
public static final String ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF = "entity_view_by_tenant_and_entity_id";
public static final String ENTITY_VIEW_KEYS_PROPERTY = "keys";
public static final String ENTITY_VIEW_TYPE_PROPERTY = "type";
public static final String ENTITY_VIEW_START_TS_PROPERTY = "start_ts";
public static final String ENTITY_VIEW_END_TS_PROPERTY = "end_ts";
public static final String ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "entity_view_by_tenant_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF = "entity_view_by_tenant_by_type_and_search_text";
public static final String ENTITY_VIEW_BY_TENANT_AND_NAME = "entity_view_by_tenant_and_name";
/**

7
dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java

@ -41,6 +41,7 @@ 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;
@ -71,6 +72,10 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
@Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@PartitionKey(value = 3)
@Column(name = DEVICE_TYPE_PROPERTY)
private String type;
@Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY)
private UUID entityId;
@ -113,6 +118,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
if (entityView.getCustomerId() != null) {
this.customerId = entityView.getCustomerId().getId();
}
this.type = entityView.getType();
this.name = entityView.getName();
try {
this.keys = mapper.writeValueAsString(entityView.getKeys());
@ -143,6 +149,7 @@ public class EntityViewEntity implements SearchTextEntity<EntityView> {
if (customerId != null) {
entityView.setCustomerId(new CustomerId(customerId));
}
entityView.setType(type);
entityView.setName(name);
try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java

@ -69,6 +69,9 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
@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;
@ -108,6 +111,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
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());
@ -144,6 +148,7 @@ public class EntityViewEntity extends BaseSqlEntity<EntityView> implements Searc
if (customerId != null) {
entityView.setCustomerId(new CustomerId(toUUID(customerId)));
}
entityView.setType(type);
entityView.setName(name);
try {
entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class));

41
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java

@ -19,8 +19,6 @@ 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.EntityView;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.model.sql.EntityViewEntity;
import org.thingsboard.server.dao.util.SqlDao;
@ -36,21 +34,46 @@ public interface EntityViewRepository extends CrudRepository<EntityViewEntity, S
"AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
"AND e.id > :idOffset ORDER BY e.id")
List<EntityViewEntity> findByTenantId(@Param("tenantId") String tenantId,
@Param("textSearch") String textSearch,
@Param("idOffset") String idOffset,
Pageable pageable);
@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<EntityViewEntity> 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<EntityViewEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
@Param("customerId") String customerId,
@Param("searchText") String searchText,
@Param("idOffset") String idOffset,
Pageable pageable);
@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<EntityViewEntity> 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);
List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId);
@Query("SELECT DISTINCT ev.type FROM EntityViewEntity ev WHERE ev.tenantId = :tenantId")
List<String> findTenantEntityViewTypes(@Param("tenantId") String tenantId);
}

42
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java

@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
@ -41,7 +40,6 @@ import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
/**
@ -75,6 +73,17 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
new PageRequest(0, pageLink.getLimit())));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
return DaoUtil.convertDataList(
entityViewRepository.findByTenantIdAndType(
fromTimeUUID(tenantId),
type,
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override
public Optional<EntityView> findEntityViewByTenantIdAndName(UUID tenantId, String name) {
return Optional.ofNullable(
@ -95,9 +104,38 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
return DaoUtil.convertDataList(
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())
));
}
@Override
public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) {
return service.submit(() -> DaoUtil.convertDataList(
entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId))));
}
@Override
public ListenableFuture<List<EntitySubtype>> findTenantEntityViewTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantEntityViewTypesToDto(tenantId, entityViewRepository.findTenantEntityViewTypes(fromTimeUUID(tenantId))));
}
private List<EntitySubtype> convertTenantEntityViewTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
list = new ArrayList<>();
for (String type : types) {
list.add(new EntitySubtype(new TenantId(tenantId), EntityType.ENTITY_VIEW, type));
}
}
return list;
}
}

23
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -87,7 +87,11 @@ public class BaseTimeseriesService implements TimeseriesService {
.map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC"))
.collect(Collectors.toList());
return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
if (queries.size() > 0) {
return timeseriesDao.findAllAsync(entityView.getEntityId(), queries);
} else {
return Futures.immediateFuture(new ArrayList<>());
}
}
keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
return Futures.allAsList(futures);
@ -133,11 +137,20 @@ public class BaseTimeseriesService implements TimeseriesService {
private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) {
return queries.stream().map(query -> {
long startTs = entityView.getStartTimeMs() == 0 ? query.getStartTs() : entityView.getStartTimeMs();
long endTs = entityView.getEndTimeMs() == 0 ? query.getEndTs() : entityView.getEndTimeMs();
long startTs;
if (entityView.getStartTimeMs() != 0 && entityView.getStartTimeMs() > query.getStartTs()) {
startTs = entityView.getStartTimeMs();
} else {
startTs = query.getStartTs();
}
return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query :
new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
long endTs;
if (entityView.getEndTimeMs() != 0 && entityView.getEndTimeMs() < query.getEndTs()) {
endTs = entityView.getEndTimeMs();
} else {
endTs = query.getEndTs();
}
return new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
}).collect(Collectors.toList());
}

49
dao/src/main/resources/cassandra/schema-entities.cql

@ -624,61 +624,90 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
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)
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_views
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)
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_views
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)
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_views
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)
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_views
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)
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);

240
dao/src/main/resources/cassandra/system-data.cql

File diff suppressed because one or more lines are too long

3
dao/src/main/resources/sql/schema-entities.sql

@ -228,12 +228,13 @@ CREATE TABLE IF NOT EXISTS rule_node (
search_text varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_views (
CREATE TABLE IF NOT EXISTS entity_view (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
type varchar(255),
name varchar(255),
keys varchar(255),
start_ts bigint,

2
dao/src/test/resources/sql/drop-all-tables.sql

@ -19,4 +19,4 @@ DROP TABLE IF EXISTS widget_type;
DROP TABLE IF EXISTS widgets_bundle;
DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS entity_views;
DROP TABLE IF EXISTS entity_view;

5
msa/docker/.env

@ -1,5 +1,10 @@
DOCKER_REPO=local-maven-build
JS_EXECUTOR_DOCKER_NAME=tb-js-executor
TB_NODE_DOCKER_NAME=tb-node
WEB_UI_DOCKER_NAME=tb-web-ui
TB_VERSION=2.2.0-SNAPSHOT
KAFKA_TOPICS=js.eval.requests:100:1

16
msa/docker/docker-compose.yml

@ -15,7 +15,7 @@
#
version: '2'
version: '2.2'
services:
zookeeper:
@ -40,7 +40,8 @@ services:
- zookeeper
tb-js-executor:
restart: always
image: "${DOCKER_REPO}/tb-js-executor:${TB_VERSION}"
image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}"
scale: 20
environment:
TB_KAFKA_SERVERS: kafka:9092
env_file:
@ -49,11 +50,16 @@ services:
- kafka
tb:
restart: always
image: "${DOCKER_REPO}/tb-node:${TB_VERSION}"
image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
ports:
- "8080"
- "1883:1883"
- "5683:5683/udp"
logging:
driver: "json-file"
options:
max-size: "200m"
max-file: "30"
env_file:
- tb-node.env
environment:
@ -68,7 +74,7 @@ services:
- kafka
tb-web-ui1:
restart: always
image: "${DOCKER_REPO}/tb-web-ui:${TB_VERSION}"
image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
ports:
- "8080"
environment:
@ -78,7 +84,7 @@ services:
- tb-web-ui.env
tb-web-ui2:
restart: always
image: "${DOCKER_REPO}/tb-web-ui:${TB_VERSION}"
image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
ports:
- "8080"
environment:

2
msa/docker/docker-start-services.sh

@ -17,4 +17,4 @@
./check-dirs.sh
docker-compose up -d --scale tb-js-executor=20
docker-compose up -d

1
msa/docker/docker-update-service.sh

@ -15,4 +15,5 @@
# limitations under the License.
#
docker-compose pull $@
docker-compose up -d --no-deps --build $@

3
msa/docker/haproxy/config/haproxy.cfg

@ -35,8 +35,9 @@ frontend http-in
reqadd X-Forwarded-Proto:\ http
acl transport_http_acl path_beg /api/v1/
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
redirect scheme https if !letsencrypt_http_acl
redirect scheme https if !letsencrypt_http_acl !transport_http_acl
use_backend letsencrypt_http if letsencrypt_http_acl
default_backend tb-web-backend

2
msa/docker/tb-node/conf/thingsboard.conf

@ -19,6 +19,6 @@ export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/gc.log -XX:+IgnoreUnre
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
export LOG_FILENAME=thingsboard.out
export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions

2
msa/js-executor/docker/Dockerfile

@ -23,4 +23,6 @@ RUN chmod a+x /tmp/*.sh \
RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
CMD ["start-js-executor.sh"]

3
msa/js-executor/pom.xml

@ -35,6 +35,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<pkg.name>tb-js-executor</pkg.name>
<docker.name>tb-js-executor</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
@ -290,7 +291,7 @@
</executions>
<configuration>
<skip>${dockerfile.skip}</skip>
<repository>${docker.repo}/${pkg.name}</repository>
<repository>${docker.repo}/${docker.name}</repository>
<tag>${project.version}</tag>
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>

2
msa/js-executor/src/main/scripts/control/deb/postinst

@ -2,5 +2,5 @@
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
# update-rc.d ${pkg.name} defaults
update-rc.d ${pkg.name} defaults

3
msa/tb-node/pom.xml

@ -35,6 +35,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<pkg.name>thingsboard</pkg.name>
<docker.name>tb-node</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
@ -114,7 +115,7 @@
</executions>
<configuration>
<skip>${dockerfile.skip}</skip>
<repository>${docker.repo}/tb-node</repository>
<repository>${docker.repo}/${docker.name}</repository>
<tag>${project.version}</tag>
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>

2
msa/web-ui/docker/Dockerfile

@ -23,4 +23,6 @@ RUN chmod a+x /tmp/*.sh \
RUN dpkg -i /tmp/${pkg.name}.deb
RUN update-rc.d ${pkg.name} disable
CMD ["start-web-ui.sh"]

3
msa/web-ui/pom.xml

@ -35,6 +35,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<pkg.name>tb-web-ui</pkg.name>
<docker.name>tb-web-ui</docker.name>
<pkg.user>thingsboard</pkg.user>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
@ -314,7 +315,7 @@
</executions>
<configuration>
<skip>${dockerfile.skip}</skip>
<repository>${docker.repo}/${pkg.name}</repository>
<repository>${docker.repo}/${docker.name}</repository>
<tag>${project.version}</tag>
<verbose>true</verbose>
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>

2
msa/web-ui/src/main/scripts/control/deb/postinst

@ -2,5 +2,5 @@
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
# update-rc.d ${pkg.name} defaults
update-rc.d ${pkg.name} defaults

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java

@ -48,7 +48,7 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "copy attributes",
name = "copy to view",
configClazz = EmptyNodeConfiguration.class,
nodeDescription = "Copy attributes from asset/device to entity view and changes message originator to related entity view",
nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " +

2
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -37,7 +37,7 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@Service("CoapTransportService")
@ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(prefix = "transport.coap", value = "enabled", havingValue = "true")
@Slf4j
public class CoapTransportService {

2
transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.http;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -34,6 +35,7 @@ import javax.servlet.http.HttpServletRequest;
* @author Andrew Shvayka
*/
@RestController
@ConditionalOnProperty(prefix = "transport.http", value = "enabled", havingValue = "true")
@RequestMapping("/api/v1")
@Slf4j
public class DeviceApiController {

4
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@ -41,6 +42,7 @@ import java.util.concurrent.Executors;
* Created by ashvayka on 04.10.18.
*/
@Slf4j
@ConditionalOnProperty(prefix = "transport.mqtt", value = "enabled", havingValue = "true", matchIfMissing = true)
@Component
@Data
public class MqttTransportContext {
@ -59,7 +61,7 @@ public class MqttTransportContext {
@Autowired
private MqttTransportAdaptor adaptor;
@Value("${mqtt.netty.max_payload_size}")
@Value("${transport.mqtt.netty.max_payload_size}")
private Integer maxPayloadSize;
@Value("${cluster.node_id:#{null}}")

7
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -55,6 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler;
import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
import org.thingsboard.server.transport.mqtt.util.SslUtil;
import javax.net.ssl.SSLPeerUnverifiedException;
@ -93,7 +94,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private final TransportService transportService;
private final QuotaService quotaService;
private final SslHandler sslHandler;
private final ConcurrentMap<String, Integer> mqttQoSMap;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
private volatile SessionInfoProto sessionInfo;
private volatile InetSocketAddress address;
@ -295,7 +296,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private void registerSubQoS(String topic, List<Integer> grantedQoSList, MqttQoS reqQoS) {
grantedQoSList.add(getMinSupportedQos(reqQoS));
mqttQoSMap.put(topic, getMinSupportedQos(reqQoS));
mqttQoSMap.put(new MqttTopicMatcher(topic), getMinSupportedQos(reqQoS));
}
private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
@ -304,7 +305,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
for (String topicName : mqttMsg.payload().topics()) {
mqttQoSMap.remove(topicName);
mqttQoSMap.remove(new MqttTopicMatcher(topicName));
try {
switch (topicName) {
case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: {

17
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java

@ -44,25 +44,22 @@ import javax.annotation.PreDestroy;
* @author Andrew Shvayka
*/
@Service("MqttTransportService")
@ConditionalOnProperty(prefix = "mqtt", value = "enabled", havingValue = "true", matchIfMissing = false)
@ConditionalOnProperty(prefix = "transport.mqtt", value = "enabled", havingValue = "true", matchIfMissing = true)
@Slf4j
public class MqttTransportService {
private static final String V1 = "v1";
private static final String DEVICE = "device";
@Value("${mqtt.bind_address}")
@Value("${transport.mqtt.bind_address}")
private String host;
@Value("${mqtt.bind_port}")
@Value("${transport.mqtt.bind_port}")
private Integer port;
@Value("${mqtt.adaptor}")
@Value("${transport.mqtt.adaptor}")
private String adaptorName;
@Value("${mqtt.netty.leak_detector_level}")
@Value("${transport.mqtt.netty.leak_detector_level}")
private String leakDetectorLevel;
@Value("${mqtt.netty.boss_group_thread_count}")
@Value("${transport.mqtt.netty.boss_group_thread_count}")
private Integer bossGroupThreadCount;
@Value("${mqtt.netty.worker_group_thread_count}")
@Value("${transport.mqtt.netty.worker_group_thread_count}")
private Integer workerGroupThreadCount;
@Autowired

11
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -36,19 +36,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private ChannelHandlerContext channel;
private AtomicInteger msgIdSeq = new AtomicInteger(0);
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<String, Integer> mqttQoSMap) {
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
super(sessionId, mqttQoSMap);
}
private void logAndWrap(AdaptorException e) throws SessionException {
log.warn("Failed to convert msg: {}", e.getMessage(), e);
throw new SessionException(e);
}
private void pushToNetwork(MqttMessage msg) {
channel.writeAndFlush(msg);
}
public void setChannel(ChannelHandlerContext channel) {
this.channel = channel;
}

2
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java

@ -33,7 +33,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
private final GatewaySessionHandler parent;
private final SessionInfoProto sessionInfo;
public GatewayDeviceSessionCtx(GatewaySessionHandler parent, DeviceInfoProto deviceInfo, ConcurrentMap<String, Integer> mqttQoSMap) {
public GatewayDeviceSessionCtx(GatewaySessionHandler parent, DeviceInfoProto deviceInfo, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
super(UUID.randomUUID(), mqttQoSMap);
this.parent = parent;
this.sessionInfo = SessionInfoProto.newBuilder()

2
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java

@ -68,7 +68,7 @@ public class GatewaySessionHandler {
private final DeviceInfoProto gateway;
private final UUID sessionId;
private final Map<String, GatewayDeviceSessionCtx> devices;
private final ConcurrentMap<String, Integer> mqttQoSMap;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
private final ChannelHandlerContext channel;
private final DeviceSessionCtx deviceSessionCtx;

21
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java

@ -16,35 +16,38 @@
package org.thingsboard.server.transport.mqtt.session;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
/**
* Created by ashvayka on 30.08.18.
*/
public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionContext {
private final ConcurrentMap<String, Integer> mqttQoSMap;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
public MqttDeviceAwareSessionContext(UUID sessionId, ConcurrentMap<String, Integer> mqttQoSMap) {
public MqttDeviceAwareSessionContext(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
super(sessionId);
this.mqttQoSMap = mqttQoSMap;
}
public ConcurrentMap<String, Integer> getMqttQoSMap() {
public ConcurrentMap<MqttTopicMatcher, Integer> getMqttQoSMap() {
return mqttQoSMap;
}
public MqttQoS getQoSForTopic(String topic) {
Integer qos = mqttQoSMap.get(topic);
if (qos != null) {
return MqttQoS.valueOf(qos);
List<Integer> qosList = mqttQoSMap.entrySet()
.stream()
.filter(entry -> entry.getKey().matches(topic))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
if (!qosList.isEmpty()) {
return MqttQoS.valueOf(qosList.get(0));
} else {
return MqttQoS.AT_LEAST_ONCE;
}

55
transport/mqtt-common/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttTopicMatcher.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.session;
import java.util.regex.Pattern;
public class MqttTopicMatcher {
private final String topic;
private final Pattern topicRegex;
public MqttTopicMatcher(String topic) {
if(topic == null){
throw new NullPointerException("topic");
}
this.topic = topic;
this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$");
}
public String getTopic() {
return topic;
}
public boolean matches(String topic){
return this.topicRegex.matcher(topic).matches();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MqttTopicMatcher that = (MqttTopicMatcher) o;
return topic.equals(that.topic);
}
@Override
public int hashCode() {
return topic.hashCode();
}
}

53
transport/mqtt-transport/src/main/resources/tb-mqtt-transport.yml

@ -17,32 +17,31 @@
spring.main.web-application-type: none
# MQTT server parameters
mqtt:
# Enable/disable mqtt transport protocol.
enabled: "${MQTT_ENABLED:true}"
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}"
adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
timeout: "${MQTT_TIMEOUT:10000}"
netty:
leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
# MQTT SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${MQTT_SSL_ENABLED:false}"
# SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
# Path to the key store that holds the SSL certificate
key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
# Password used to access the key store
key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
transport:
mqtt:
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}"
adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
timeout: "${MQTT_TIMEOUT:10000}"
netty:
leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}"
# MQTT SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${MQTT_SSL_ENABLED:false}"
# SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext
protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}"
# Path to the key store that holds the SSL certificate
key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
# Password used to access the key store
key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
#Quota parameters
quota:
@ -56,7 +55,7 @@ quota:
# Interval for scheduled task that cleans expired records. TTL is used for expiring
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
# Enable Host API Limits
enabled: "${QUOTA_HOST_ENABLED:false}"
enabled: "${QUOTA_HOST_ENABLED:true}"
# Array of whitelist hosts
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
# Array of blacklist hosts

33
ui/src/app/api/entity.service.js

@ -533,6 +533,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
);
break;
case types.aliasFilterType.entityViewType.value:
getEntitiesByNameFilter(types.entityType.entityView, filter.entityViewNameFilter, maxItems, {ignoreLoading: true}, filter.entityViewType).then(
function success(entities) {
if (entities && entities.length || !failOnEmpty) {
result.entities = entitiesToEntitiesInfo(entities);
deferred.resolve(result);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
break;
case types.aliasFilterType.relationsQuery.value:
result.stateEntity = filter.rootStateEntity;
var rootEntityType;
@ -578,6 +593,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
break;
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
result.stateEntity = filter.rootStateEntity;
if (result.stateEntity && stateEntityId) {
rootEntityType = stateEntityId.entityType;
@ -604,6 +620,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
} else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
searchQuery.deviceTypes = filter.deviceTypes;
findByQueryPromise = deviceService.findByQuery(searchQuery, false, {ignoreLoading: true});
} else if (filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
searchQuery.entityViewTypes = filter.entityViewTypes;
findByQueryPromise = entityViewService.findByQuery(searchQuery, false, {ignoreLoading: true});
}
findByQueryPromise.then(
function success(entities) {
@ -646,6 +665,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceType.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
case types.aliasFilterType.entityViewType.value:
return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
case types.aliasFilterType.relationsQuery.value:
if (filter.filters && filter.filters.length) {
var match = false;
@ -671,6 +692,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
case types.aliasFilterType.deviceSearchQuery.value:
return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
case types.aliasFilterType.entityViewSearchQuery.value:
return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
}
}
return false;
@ -690,12 +713,16 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return entityType === types.entityType.asset;
case types.aliasFilterType.deviceType.value:
return entityType === types.entityType.device;
case types.aliasFilterType.entityViewType.value:
return entityType === types.entityType.entityView;
case types.aliasFilterType.relationsQuery.value:
return true;
case types.aliasFilterType.assetSearchQuery.value:
return entityType === types.entityType.asset;
case types.aliasFilterType.deviceSearchQuery.value:
return entityType === types.entityType.device;
case types.aliasFilterType.entityViewSearchQuery.value:
return entityType === types.entityType.entityView;
}
return false;
}
@ -1046,6 +1073,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return assetService.deleteAsset(entityId.id);
} else if (entityId.entityType == types.entityType.device) {
return deviceService.deleteDevice(entityId.id);
} else if (entityId.entityType == types.entityType.entityView) {
return entityViewService.deleteEntityView(entityId.id);
}
}
@ -1151,6 +1180,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return assetService.saveAsset(entity);
} else if (entityType == types.entityType.device) {
return deviceService.saveDevice(entity);
} else if (entityType == types.entityType.entityView) {
return entityViewService.saveEntityView(entity);
}
}
@ -1279,6 +1310,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
searchQuery.assetTypes = entitySubTypes;
} else if (entityType == types.entityType.device) {
searchQuery.deviceTypes = entitySubTypes;
} else if (entityType == types.entityType.entityView) {
searchQuery.entityViewTypes = entitySubTypes;
} else {
return null; //Not supported
}

8
ui/src/app/api/subscription.js

@ -172,9 +172,9 @@ export default class Subscription {
if (this.type === this.ctx.types.widgetType.rpc.value) {
if (this.targetDeviceId) {
entityId = {
entityType: this.ctx.entityType.device,
entityType: this.ctx.types.entityType.device,
id: this.targetDeviceId
}
};
entityName = this.targetDeviceName;
}
} else if (this.type == this.ctx.types.widgetType.alarm.value) {
@ -182,7 +182,7 @@ export default class Subscription {
entityId = {
entityType: this.alarmSource.entityType,
id: this.alarmSource.entityId
}
};
entityName = this.alarmSource.entityName;
}
} else {
@ -192,7 +192,7 @@ export default class Subscription {
entityId = {
entityType: datasource.entityType,
id: datasource.entityId
}
};
entityName = datasource.entityName;
break;
}

53
ui/src/app/api/time.service.js

@ -26,15 +26,17 @@ const MIN_INTERVAL = SECOND;
const MAX_INTERVAL = 365 * 20 * DAY;
const MIN_LIMIT = 10;
const AVG_LIMIT = 200;
const MAX_LIMIT = 500;
//const AVG_LIMIT = 200;
//const MAX_LIMIT = 500;
/*@ngInject*/
function TimeService($translate, types) {
function TimeService($translate, $http, $q, types) {
var predefIntervals;
var maxDatapointsLimit;
var service = {
loadMaxDatapointsLimit: loadMaxDatapointsLimit,
minIntervalLimit: minIntervalLimit,
maxIntervalLimit: maxIntervalLimit,
boundMinInterval: boundMinInterval,
@ -45,20 +47,38 @@ function TimeService($translate, types) {
defaultTimewindow: defaultTimewindow,
toHistoryTimewindow: toHistoryTimewindow,
createSubscriptionTimewindow: createSubscriptionTimewindow,
avgAggregationLimit: function () {
return AVG_LIMIT;
getMaxDatapointsLimit: function () {
return maxDatapointsLimit;
},
getMinDatapointsLimit: function () {
return MIN_LIMIT;
}
}
return service;
function loadMaxDatapointsLimit() {
var deferred = $q.defer();
var url = '/api/dashboard/maxDatapointsLimit';
$http.get(url, {ignoreLoading: true}).then(function success(response) {
maxDatapointsLimit = response.data;
if (!maxDatapointsLimit || maxDatapointsLimit <= MIN_LIMIT) {
maxDatapointsLimit = MIN_LIMIT + 1;
}
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function minIntervalLimit(timewindow) {
var min = timewindow / MAX_LIMIT;
var min = timewindow / 500;
return boundMinInterval(min);
}
function avgInterval(timewindow) {
var avg = timewindow / AVG_LIMIT;
var avg = timewindow / 200;
return boundMinInterval(avg);
}
@ -230,7 +250,7 @@ function TimeService($translate, types) {
},
aggregation: {
type: types.aggregation.avg.value,
limit: AVG_LIMIT
limit: Math.floor(maxDatapointsLimit / 2)
}
}
return timewindow;
@ -246,22 +266,27 @@ function TimeService($translate, types) {
}
var aggType;
var limit;
if (timewindow.aggregation) {
aggType = timewindow.aggregation.type || types.aggregation.avg.value;
limit = timewindow.aggregation.limit || maxDatapointsLimit;
} else {
aggType = types.aggregation.avg.value;
limit = maxDatapointsLimit;
}
var historyTimewindow = {
history: {
fixedTimewindow: {
startTimeMs: startTimeMs,
endTimeMs: endTimeMs
},
interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, aggType)
interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, types.aggregation.avg.value)
},
aggregation: {
type: aggType
type: aggType,
limit: limit
}
}
@ -275,7 +300,7 @@ function TimeService($translate, types) {
realtimeWindowMs: null,
aggregation: {
interval: SECOND,
limit: AVG_LIMIT,
limit: maxDatapointsLimit,
type: types.aggregation.avg.value
}
};
@ -283,14 +308,14 @@ function TimeService($translate, types) {
if (stateData) {
subscriptionTimewindow.aggregation = {
interval: SECOND,
limit: MAX_LIMIT,
limit: maxDatapointsLimit,
type: types.aggregation.none.value,
stateData: true
};
} else {
subscriptionTimewindow.aggregation = {
interval: SECOND,
limit: AVG_LIMIT,
limit: maxDatapointsLimit,
type: types.aggregation.avg.value
};
}
@ -298,7 +323,7 @@ function TimeService($translate, types) {
if (angular.isDefined(timewindow.aggregation) && !stateData) {
subscriptionTimewindow.aggregation = {
type: timewindow.aggregation.type || types.aggregation.avg.value,
limit: timewindow.aggregation.limit || AVG_LIMIT
limit: timewindow.aggregation.limit || maxDatapointsLimit
};
}
if (angular.isDefined(timewindow.realtime)) {

3
ui/src/app/api/user.service.js

@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
.name;
/*@ngInject*/
function UserService($http, $q, $rootScope, adminService, dashboardService, loginService, toast, store, jwtHelper, $translate, $state, $location) {
function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location) {
var currentUser = null,
currentUserDetails = null,
lastPublicDashboardId = null,
@ -390,6 +390,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
function loadSystemParams() {
var promises = [];
promises.push(loadIsUserTokenAccessEnabled());
promises.push(timeService.loadMaxDatapointsLimit());
return $q.all(promises);
}

8
ui/src/app/common/types.constant.js

@ -253,6 +253,10 @@ export default angular.module('thingsboard.types', [])
value: 'deviceType',
name: 'alias.filter-type-device-type'
},
entityViewType: {
value: 'entityViewType',
name: 'alias.filter-type-entity-view-type'
},
relationsQuery: {
value: 'relationsQuery',
name: 'alias.filter-type-relations-query'
@ -264,6 +268,10 @@ export default angular.module('thingsboard.types', [])
deviceSearchQuery: {
value: 'deviceSearchQuery',
name: 'alias.filter-type-device-search-query'
},
entityViewSearchQuery: {
value: 'entityViewSearchQuery',
name: 'alias.filter-type-entity-view-search-query'
}
},
position: {

10
ui/src/app/components/timewindow-panel.controller.js

@ -31,6 +31,8 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic
vm.maxRealtimeAggInterval = maxRealtimeAggInterval;
vm.minHistoryAggInterval = minHistoryAggInterval;
vm.maxHistoryAggInterval = maxHistoryAggInterval;
vm.minDatapointsLimit = minDatapointsLimit;
vm.maxDatapointsLimit = maxDatapointsLimit;
if (vm.historyOnly) {
vm.timewindow.selectedTab = 1;
@ -86,6 +88,14 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic
return timeService.maxIntervalLimit(currentHistoryTimewindow());
}
function minDatapointsLimit () {
return timeService.getMinDatapointsLimit();
}
function maxDatapointsLimit () {
return timeService.getMaxDatapointsLimit();
}
function currentHistoryTimewindow() {
if (vm.timewindow.history.historyType === 0) {
return vm.timewindow.history.timewindowMs;

15
ui/src/app/components/timewindow-panel.tpl.html

@ -60,19 +60,22 @@
</md-option>
</md-select>
</md-input-container>
<md-slider-container ng-show="vm.showLimit()">
<md-slider-container ng-if="vm.showLimit()">
<span translate>aggregation.limit</span>
<md-slider flex min="10" max="500" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider">
<md-slider flex min="{{vm.minDatapointsLimit()}}" max="{{vm.maxDatapointsLimit()}}" step="1"
ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider">
</md-slider>
<md-input-container>
<input flex type="number" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider">
<md-input-container style="max-width: 80px;">
<input flex type="number" step="1"
min="{{vm.minDatapointsLimit()}}" max="{{vm.maxDatapointsLimit()}}"
ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider">
</md-input-container>
</md-slider-container>
<tb-timeinterval ng-show="vm.showRealtimeAggInterval()" min="vm.minRealtimeAggInterval()" max="vm.maxRealtimeAggInterval()"
<tb-timeinterval ng-if="vm.showRealtimeAggInterval()" min="vm.minRealtimeAggInterval()" max="vm.maxRealtimeAggInterval()"
predefined-name="'aggregation.group-interval'"
ng-model="vm.timewindow.realtime.interval">
</tb-timeinterval>
<tb-timeinterval ng-show="vm.showHistoryAggInterval()" min="vm.minHistoryAggInterval()" max="vm.maxHistoryAggInterval()"
<tb-timeinterval ng-if="vm.showHistoryAggInterval()" min="vm.minHistoryAggInterval()" max="vm.maxHistoryAggInterval()"
predefined-name="'aggregation.group-interval'"
ng-model="vm.timewindow.history.interval">
</tb-timeinterval>

2
ui/src/app/components/timewindow.directive.js

@ -228,7 +228,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
if (angular.isDefined(value.aggregation.type) && value.aggregation.type.length > 0) {
model.aggregation.type = value.aggregation.type;
}
model.aggregation.limit = value.aggregation.limit || timeService.avgAggregationLimit();
model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2);
}
}
scope.updateDisplayValue();

22
ui/src/app/entity-view/entity-view-fieldset.tpl.html

@ -52,12 +52,22 @@
<div translate ng-message="required">entity-view.name-required</div>
</div>
</md-input-container>
<tb-entity-select flex ng-disabled="!isEdit"
the-form="theForm"
tb-required="true"
allowed-entity-types="allowedEntityTypes"
ng-model="entityView.entityId">
</tb-entity-select>
<tb-entity-subtype-autocomplete
ng-disabled="$root.loading || !isEdit"
tb-required="true"
the-form="theForm"
ng-model="entityView.type"
entity-type="types.entityType.entityView">
</tb-entity-subtype-autocomplete>
<section layout="column">
<label translate class="tb-title no-padding">entity-view.related-entity</label>
<tb-entity-select flex ng-disabled="!isEdit"
the-form="theForm"
tb-required="true"
allowed-entity-types="allowedEntityTypes"
ng-model="entityView.entityId">
</tb-entity-select>
</section>
<md-input-container class="md-block">
<label translate>entity-view.description</label>
<textarea ng-model="entityView.additionalInfo.description" rows="2"></textarea>

4
ui/src/app/entity-view/entity-view.routes.js

@ -42,7 +42,7 @@ export default function EntityViewRoutes($stateProvider, types) {
pageTitle: 'entity-view.entity-views'
},
ncyBreadcrumb: {
label: '{"icon": "view_stream", "label": "entity-view.entity-views"}'
label: '{"icon": "view_quilt", "label": "entity-view.entity-views"}'
}
})
.state('home.customers.entityViews', {
@ -65,7 +65,7 @@ export default function EntityViewRoutes($stateProvider, types) {
pageTitle: 'customer.entity-views'
},
ncyBreadcrumb: {
label: '{"icon": "view_stream", "label": "{{ vm.customerEntityViewsTitle }}", "translate": "false"}'
label: '{"icon": "view_quilt", "label": "{{ vm.customerEntityViewsTitle }}", "translate": "false"}'
}
});

22
ui/src/app/entity/entity-filter-view.directive.js

@ -77,6 +77,15 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
}
break;
case types.aliasFilterType.entityViewType.value:
var entityViewType = scope.filter.entityViewType;
prefix = scope.filter.entityViewNameFilter;
if (prefix && prefix.length) {
scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-and-name-description', {entityViewType: entityViewType, prefix: prefix});
} else {
scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-type-description', {entityViewType: entityViewType});
}
break;
case types.aliasFilterType.relationsQuery.value:
var rootEntityText;
var directionText;
@ -134,6 +143,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
break;
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
allEntitiesText = $translate.instant('alias.all-entities');
anyRelationText = $translate.instant('alias.any-relation');
if (scope.filter.rootStateEntity) {
@ -165,7 +175,7 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
translationValues
);
} else {
} else if (scope.filter.type == types.aliasFilterType.deviceSearchQuery.value) {
var deviceTypesQuoted = [];
scope.filter.deviceTypes.forEach(function(deviceType) {
deviceTypesQuoted.push("'"+deviceType+"'");
@ -175,6 +185,16 @@ export default function EntityFilterViewDirective($compile, $templateCache, $q,
scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
translationValues
);
} else if (scope.filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
var entityViewTypesQuoted = [];
scope.filter.entityViewTypes.forEach(function(entityViewType) {
entityViewTypesQuoted.push("'"+entityViewType+"'");
});
var entityViewTypesText = entityViewTypesQuoted.join(', ');
translationValues.entityViewTypes = entityViewTypesText;
scope.filterDisplayValue = $translate.instant('alias.filter-type-entity-view-search-query-description',
translationValues
);
}
break;
default:

8
ui/src/app/entity/entity-filter.directive.js

@ -69,9 +69,14 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
filter.deviceType = null;
filter.deviceNameFilter = '';
break;
case types.aliasFilterType.entityViewType.value:
filter.entityViewType = null;
filter.entityViewNameFilter = '';
break;
case types.aliasFilterType.relationsQuery.value:
case types.aliasFilterType.assetSearchQuery.value:
case types.aliasFilterType.deviceSearchQuery.value:
case types.aliasFilterType.entityViewSearchQuery.value:
filter.rootStateEntity = false;
filter.stateEntityParamName = null;
filter.defaultStateEntity = null;
@ -86,6 +91,9 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc
} else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
filter.relationType = null;
filter.deviceTypes = [];
} else if (filter.type === types.aliasFilterType.entityViewSearchQuery.value) {
filter.relationType = null;
filter.entityViewTypes = [];
}
break;
}

83
ui/src/app/entity/entity-filter.tpl.html

@ -112,6 +112,20 @@
aria-label="{{ 'device.name-starts-with' | translate }}">
</md-input-container>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewType.value" id="entityViewTypeFilter">
<tb-entity-subtype-autocomplete
tb-required="true"
the-form="theForm"
ng-model="filter.entityViewType"
entity-type="types.entityType.entityView">
</tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>entity-view.name-starts-with</label>
<input name="entityViewNameFilter"
ng-model="filter.entityViewNameFilter"
aria-label="{{ 'entity-view.name-starts-with' | translate }}">
</md-input-container>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
<section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
@ -311,4 +325,73 @@
ng-model="filter.deviceTypes">
</tb-entity-subtype-list>
</section>
<section layout="column" ng-if="filter.type == types.aliasFilterType.entityViewSearchQuery.value" id="entityViewSearchQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
<section class="tb-root-state-entity-switch" layout="row" layout-align="start center" style="padding-left: 0px;">
<md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
aria-label="{{ 'alias.root-state-entity' | translate }}">
</md-switch>
<label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
</section>
<div flex layout="row" ng-if="!filter.rootStateEntity">
<tb-entity-select flex
the-form="theForm"
tb-required="!filter.rootStateEntity"
ng-disabled="filter.rootStateEntity"
use-alias-entity-types="true"
ng-model="filter.rootEntity">
</tb-entity-select>
</div>
<div flex layout="row" ng-if="filter.rootStateEntity">
<md-input-container class="md-block" style="margin-top: 32px;">
<label translate>alias.state-entity-parameter-name</label>
<input name="stateEntityParamName"
placeholder="{{ 'alias.default-entity-parameter-name' | translate }}"
ng-model="filter.stateEntityParamName"
aria-label="{{ 'alias.state-entity-parameter-name' | translate }}">
</md-input-container>
<div flex layout="column">
<label class="tb-small">{{ 'alias.default-state-entity' | translate }}</label>
<tb-entity-select flex
the-form="theForm"
tb-required="false"
use-alias-entity-types="true"
ng-model="filter.defaultStateEntity">
</tb-entity-select>
</div>
</div>
<div flex layout="row">
<md-input-container class="md-block" style="min-width: 100px;">
<label translate>relation.direction</label>
<md-select required ng-model="filter.direction">
<md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
{{ ('relation.search-direction.' + direction) | translate}}
</md-option>
</md-select>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alias.max-relation-level</label>
<input name="maxRelationLevel"
type="number"
min="1"
step="1"
placeholder="{{ 'alias.unlimited-level' | translate }}"
ng-model="filter.maxLevel"
aria-label="{{ 'alias.max-relation-level' | translate }}">
</md-input-container>
</div>
<div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
<tb-relation-type-autocomplete flex
hide-label
the-form="theForm"
ng-model="filter.relationType"
tb-required="false">
</tb-relation-type-autocomplete>
<div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>entity-view.entity-view-types</div>
<tb-entity-subtype-list
tb-required="true"
entity-type="types.entityType.entityView"
ng-model="filter.entityViewTypes">
</tb-entity-subtype-list>
</section>
</div>

11
ui/src/app/entity/entity-subtype-autocomplete.directive.js

@ -22,7 +22,7 @@ import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, entityViewService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
@ -96,6 +96,8 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
}
if (entitySubtypesPromise) {
entitySubtypesPromise.then(
@ -134,6 +136,13 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
scope.$on('deviceSaved', function() {
scope.entitySubtypes = null;
});
} else if (scope.entityType == types.entityType.entityView) {
scope.selectEntitySubtypeText = 'entity-view.select-entity-view-type';
scope.entitySubtypeText = 'entity-view.entity-view-type';
scope.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
scope.$on('entityViewSaved', function() {
scope.entitySubtypes = null;
});
}
}

10
ui/src/app/entity/entity-subtype-list.directive.js

@ -22,7 +22,7 @@ import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
import './entity-subtype-list.scss';
/*@ngInject*/
export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService, entityViewService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@ -47,6 +47,12 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type');
scope.noSubtypesMathingText = 'device.no-device-types-matching';
scope.subtypeListEmptyText = 'device.device-type-list-empty';
} else if (scope.entityType == types.entityType.entityView) {
scope.placeholder = scope.tbRequired ? $translate.instant('entity-view.enter-entity-view-type')
: $translate.instant('entity-view.any-entity-view');
scope.secondaryPlaceholder = '+' + $translate.instant('entity-view.entity-view-type');
scope.noSubtypesMathingText = 'entity-view.no-entity-view-types-matching';
scope.subtypeListEmptyText = 'entity-view.entity-view-type-list-empty';
}
scope.$watch('tbRequired', function () {
@ -97,6 +103,8 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
}
if (entitySubtypesPromise) {
entitySubtypesPromise.then(

11
ui/src/app/entity/entity-subtype-select.directive.js

@ -22,7 +22,7 @@ import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, entityViewService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySubtypeSelectTemplate);
@ -75,6 +75,8 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
entitySubtypesPromise = assetService.getAssetTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.device) {
entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
} else if (scope.entityType == types.entityType.entityView) {
entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
}
if (entitySubtypesPromise) {
entitySubtypesPromise.then(
@ -100,6 +102,9 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
} else if (scope.entityType == types.entityType.device) {
scope.entitySubtypeTitle = 'device.device-type';
scope.entitySubtypeRequiredText = 'device.device-type-required';
} else if (scope.entityType == types.entityType.entityView) {
scope.entitySubtypeTitle = 'entity-view.entity-view-type';
scope.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
}
scope.entitySubtypes.length = 0;
if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
@ -116,6 +121,10 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
scope.$on('deviceSaved', function() {
loadSubTypes();
});
} else if (scope.entityType == types.entityType.entityView) {
scope.$on('entityViewSaved', function() {
loadSubTypes();
});
}
}
}

1
ui/src/app/help/help-links.constant.js

@ -96,6 +96,7 @@ export default angular.module('thingsboard.help', [])
customers: helpBaseUrl + "/docs/user-guide/ui/customers",
assets: helpBaseUrl + "/docs/user-guide/ui/assets",
devices: helpBaseUrl + "/docs/user-guide/ui/devices",
entityViews: helpBaseUrl + "/docs/user-guide/ui/entity-views",
dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards",
users: helpBaseUrl + "/docs/user-guide/ui/users",
widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles",

1545
ui/src/app/locale/locale.constant-TR.json

File diff suppressed because it is too large

11
ui/src/app/locale/locale.constant-en_US.json

@ -158,12 +158,17 @@
"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",
@ -839,7 +844,8 @@
"client-attributes": "Client attributes",
"shared-attributes": "Shared attributes",
"server-attributes": "Server attributes",
"latest-timeseries": "Latest timeseries"
"latest-timeseries": "Latest timeseries",
"related-entity": "Related entity"
},
"event": {
"event-type": "Event type",
@ -1551,7 +1557,8 @@
"ko_KR": "Korean",
"ru_RU": "Russian",
"es_ES": "Spanish",
"ja_JA": "Japanese"
"ja_JA": "Japanese",
"TR": "Turkish"
}
}
}

2107
ui/src/app/locale/locale.constant-es_ES.json

File diff suppressed because it is too large

2919
ui/src/app/locale/locale.constant-fr_FR.json

File diff suppressed because it is too large

89
ui/src/app/locale/locale.constant-it_IT.json

@ -528,13 +528,13 @@
"decimals": "Numero cifre decimali",
"data-generation-func": "Funzione generazione dati",
"use-data-post-processing-func": "Use data post-processing function",
"configuration": "Data key configuration",
"configuration": "Configurazione data key",
"timeseries": "Serie temporali",
"attributes": "Attributi",
"alarm": "Campi allarme",
"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} }",
"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.",
@ -759,7 +759,7 @@
"entity": "Entità",
"message-id": "Id Messaggio",
"message-type": "Tipo Messaggio",
"data-type": "Data Type",
"data-type": "Tipo di dato",
"relation-type": "Tipo di relazione",
"metadata": "Metadati",
"data": "Dati",
@ -807,7 +807,7 @@
"add-map": "Add mapping element",
"timeseries": "Serie temporali",
"add-timeseries": "Add timeseries",
"field-required": "Field is required",
"field-required": "Campo obbligatorio",
"brokers": "Broker",
"add-broker": "Aggiungi broker",
"host": "Host",
@ -855,15 +855,15 @@
"add-attribute-request": "Add attribute request",
"attribute-updates": "Attribute updates",
"add-attribute-update": "Add attribute update",
"server-side-rpc": "Server side RPC",
"server-side-rpc": "RPC lato server",
"add-server-side-rpc-request": "Add server-side RPC request",
"device-name-filter": "Device name filter",
"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": "Client scope",
"client-scope": "Visibilità client",
"add-device": "Aggiungi dispositivo",
"opc-server": "Server",
"opc-add-server": "Aggiungi server",
@ -892,19 +892,19 @@
"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-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": "Attributes poll period (ms)",
"modbus-timeseries-poll-period": "Timeseries poll period (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": "L'indirizzo del registro deve essere compreso tra 0 e 65535.",
"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",
@ -950,7 +950,7 @@
"scroll-to-top": "Scorri verso l'alto"
},
"help": {
"goto-help-page": "Go to help page"
"goto-help-page": "Vai all'help"
},
"home": {
"home": "Home",
@ -975,9 +975,9 @@
"key-val": {
"key": "Chiave",
"value": "Valore",
"remove-entry": "Remove entry",
"add-entry": "Add entry",
"no-data": "No entries"
"remove-entry": "Rimuovi voce",
"add-entry": "Aggiungi voce",
"no-data": "Nessuna voce"
},
"layout": {
"layout": "Layout",
@ -1002,8 +1002,8 @@
},
"login": {
"login": "Login",
"request-password-reset": "Request Password Reset",
"reset-password": "Azzera Password",
"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",
@ -1014,7 +1014,7 @@
"password-reset": "Password reset",
"new-password": "Nuova password",
"new-password-again": "Ripeti nuova password",
"password-link-sent-message": "Link azzeramento password inviato con successo!",
"password-link-sent-message": "Link reset password inviato con successo!",
"email": "Email"
},
"position": {
@ -1029,7 +1029,7 @@
"current-password": "Password attuale"
},
"relation": {
"relations": "Relations",
"relations": "Relazioni",
"direction": "Direzione",
"search-direction": {
"FROM": "Da",
@ -1039,9 +1039,9 @@
"FROM": "da",
"TO": "a"
},
"from-relations": "Outbound relations",
"to-relations": "Inbound relations",
"selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected",
"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à",
@ -1049,26 +1049,26 @@
"from-entity-name": "Da nome entità",
"to-entity": "A entità",
"from-entity": "Da entità",
"delete": "Delete relation",
"relation-type": "Relation type",
"relation-type-required": "Relation type is required.",
"delete": "Elimina relazione",
"relation-type": "Tipo di relazione",
"relation-type-required": "Tipo di relazione obbligatorio.",
"any-relation-type": "Ogni tipo",
"add": "Add relation",
"edit": "Edit relation",
"delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
"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": "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."
"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",
@ -1441,7 +1441,8 @@
"it_IT": "Italiano",
"ru_RU": "Russo",
"es_ES": "Spagnolo",
"ja_JA": "Giapponese"
"ja_JA": "Giapponese",
"TR": "Turco"
}
}
}

3
ui/src/app/locale/locale.constant-ja_JA.json

@ -1457,7 +1457,8 @@
"zh_CN": "中国語",
"ru_RU": "ロシア",
"es_ES": "スペイン語",
"ja_JA": "日本語"
"ja_JA": "日本語",
"TR": "トルコ語"
}
}
}

3
ui/src/app/locale/locale.constant-ko_KR.json

@ -1335,7 +1335,8 @@
"ru_RU": "러시아어",
"es_ES": "스페인어",
"it_IT": "이탈리아 사람",
"ja_JA": "일본어"
"ja_JA": "일본어",
"TR": "터키어"
}
}
}

3
ui/src/app/locale/locale.constant-ru_RU.json

@ -1360,7 +1360,8 @@
"es_ES": "Испанский",
"it_IT": "Итальянский",
"ru_RU": "Русский",
"ja_JA": "Японский"
"ja_JA": "Японский",
"TR": "Турецкий"
}
}

3
ui/src/app/locale/locale.constant-zh_CN.json

@ -1444,7 +1444,8 @@
"ru_RU": "俄语",
"es_ES": "西班牙语",
"it_IT": "意大利",
"ja_JA": "日本"
"ja_JA": "日本",
"TR": "土耳其"
}
}
}

8
ui/src/app/services/menu.service.js

@ -171,7 +171,7 @@ function Menu(userService, $state, $rootScope) {
name: 'entity-view.entity-views',
type: 'link',
state: 'home.entityViews',
icon: 'view_stream'
icon: 'view_quilt'
},
{
name: 'widget.widget-library',
@ -238,7 +238,7 @@ function Menu(userService, $state, $rootScope) {
places: [
{
name: 'entity-view.entity-views',
icon: 'view_stream',
icon: 'view_quilt',
state: 'home.entityViews'
}
]
@ -293,7 +293,7 @@ function Menu(userService, $state, $rootScope) {
name: 'entity-view.entity-views',
type: 'link',
state: 'home.entityViews',
icon: 'view_stream'
icon: 'view_quilt'
},
{
name: 'dashboard.dashboards',
@ -328,7 +328,7 @@ function Menu(userService, $state, $rootScope) {
places: [
{
name: 'entity-view.entity-views',
icon: 'view_stream',
icon: 'view_quilt',
state: 'home.entityViews'
}
]

2
ui/src/app/widget/lib/google-map.js

@ -44,7 +44,7 @@ export default class TbGoogleMap {
function initGoogleMap() {
tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: false,
scrollwheel: true,
mapTypeId: getGoogleMapTypeId(tbMap.defaultMapType),
zoom: tbMap.defaultZoomLevel || 8
});

Loading…
Cancel
Save