Browse Source

Merge with master

pull/151/head
Andrew Shvayka 9 years ago
parent
commit
9f3de06547
  1. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 16
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  3. 36
      application/src/main/resources/thingsboard.yml
  4. 7
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  5. 7
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  6. 3
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  7. 22
      common/data/src/main/java/org/thingsboard/server/common/data/HasName.java
  8. 7
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  9. 7
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  10. 7
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  11. 4
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  12. 4
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java
  13. 10
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java
  14. 59
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java
  15. 8
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java
  16. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  17. 5
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  18. 4
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  19. 1
      dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java
  20. 6
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  21. 5
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  22. 24
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  23. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  24. 36
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  25. 93
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  26. 28
      dao/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  27. 6
      dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
  28. 43
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  29. 3
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  30. 6
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
  31. 6
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  32. 3
      dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
  33. 15
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  34. 6
      dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
  35. 6
      docker/.env
  36. 132
      docker/cassandra/cassandra.yaml
  37. 32
      docker/common/common.yaml
  38. 2
      docker/docker-compose.static.yml
  39. 28
      docker/docker-compose.yml
  40. 4
      docker/tb-cassandra-schema/Dockerfile
  41. 13
      docker/tb-cassandra-schema/Makefile
  42. 16
      docker/tb-cassandra-schema/install-schema.sh
  43. 33
      docker/tb-cassandra-schema/tb-cassandra-schema.yaml
  44. 3
      docker/tb.env
  45. 4
      docker/tb/Dockerfile
  46. 11
      docker/tb/Makefile
  47. 6
      docker/tb/run-application.sh
  48. 121
      docker/tb/tb.yaml
  49. 5
      docker/thingsboard-db-schema.env
  50. 28
      docker/thingsboard-db-schema/build_and_deploy.sh
  51. 26
      docker/thingsboard/build_and_deploy.sh
  52. 71
      docker/zookeeper/Dockerfile
  53. 9
      docker/zookeeper/Makefile
  54. 153
      docker/zookeeper/zk-gen-config.sh
  55. 25
      docker/zookeeper/zk-ok.sh
  56. 190
      docker/zookeeper/zookeeper.yaml
  57. 2
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  58. 13
      ui/src/app/api/entity-relation.service.js
  59. 113
      ui/src/app/api/entity.service.js
  60. 6
      ui/src/app/asset/assets.tpl.html
  61. 5
      ui/src/app/common/types.constant.js
  62. 26
      ui/src/app/common/utils.service.js
  63. 2
      ui/src/app/components/dashboard-autocomplete.tpl.html
  64. 2
      ui/src/app/components/plugin-select.tpl.html
  65. 2
      ui/src/app/dashboard/states/entity-state-controller.js
  66. 6
      ui/src/app/entity/attribute/attribute-table.tpl.html
  67. 2
      ui/src/app/entity/entity-aliases.controller.js
  68. 171
      ui/src/app/entity/entity-autocomplete.directive.js
  69. 25
      ui/src/app/entity/entity-autocomplete.scss
  70. 45
      ui/src/app/entity/entity-autocomplete.tpl.html
  71. 8
      ui/src/app/entity/entity-filter.directive.js
  72. 2
      ui/src/app/entity/entity-filter.tpl.html
  73. 86
      ui/src/app/entity/entity-select.directive.js
  74. 19
      ui/src/app/entity/entity-select.scss
  75. 29
      ui/src/app/entity/entity-select.tpl.html
  76. 19
      ui/src/app/entity/entity-type-select.directive.js
  77. 6
      ui/src/app/entity/index.js
  78. 43
      ui/src/app/entity/relation/add-relation-dialog.controller.js
  79. 64
      ui/src/app/entity/relation/add-relation-dialog.tpl.html
  80. 179
      ui/src/app/entity/relation/relation-table.directive.js
  81. 28
      ui/src/app/entity/relation/relation-table.scss
  82. 119
      ui/src/app/entity/relation/relation-table.tpl.html
  83. 6
      ui/src/app/locale/locale.constant-es.js
  84. 6
      ui/src/app/locale/locale.constant-ko.js
  85. 6
      ui/src/app/locale/locale.constant-ru.js
  86. 6
      ui/src/app/locale/locale.constant-zh.js
  87. 65
      ui/src/app/locale/locale.constant.js
  88. 39
      ui/src/scss/main.scss

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -343,7 +343,7 @@ public abstract class BaseController {
Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);
Alarm alarm = alarmService.findAlarmById(alarmId).get();
Alarm alarm = alarmService.findAlarmByIdAsync(alarmId).get();
checkAlarm(alarm);
return alarm;
} catch (Exception e) {

16
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java

@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
@ -127,6 +128,21 @@ public class EntityRelationController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"fromId", "fromType"})
@ResponseBody
public List<EntityRelationInfo> findInfoByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType) throws ThingsboardException {
checkParameter("fromId", strFromId);
checkParameter("fromType", strFromType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
checkEntityId(entityId);
try {
return checkNotNull(relationService.findInfoByFrom(entityId).get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
@ResponseBody

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

@ -19,12 +19,18 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port
port: "${HTTP_BIND_PORT:8080}"
# Uncomment the following section to enable ssl
# ssl:
# key-store: classpath:keystore/keystore.p12
# key-store-password: thingsboard
# keyStoreType: PKCS12
# keyAlias: tomcat
# Server SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${SSL_ENABLED:false}"
# Path to the key store that holds the SSL certificate
key-store: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
# Password used to access the key store
key-store-password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
# Type of the key store
key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}"
# Alias that identifies the key in the key store
key-alias: "${SSL_KEY_ALIAS:tomcat}"
# Zookeeper connection parameters. Used for service discovery.
zk:
@ -79,12 +85,18 @@ mqtt:
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}"
# Uncomment the following lines to enable ssl for MQTT
# ssl:
# key_store: mqttserver.jks
# key_store_password: server_ks_password
# key_password: server_key_password
# key_store_type: JKS
# MQTT SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${MQTT_SSL_ENABLED:false}"
# 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:

7
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
public class Customer extends ContactBased<CustomerId>{
public class Customer extends ContactBased<CustomerId> implements HasName {
private static final long serialVersionUID = -1599722990298929275L;
@ -59,6 +59,11 @@ public class Customer extends ContactBased<CustomerId>{
this.title = title;
}
@Override
public String getName() {
return title;
}
public JsonNode getAdditionalInfo() {
return additionalInfo;
}

7
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
public class DashboardInfo extends SearchTextBased<DashboardId> {
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
private TenantId tenantId;
private CustomerId customerId;
@ -64,6 +64,11 @@ public class DashboardInfo extends SearchTextBased<DashboardId> {
this.title = title;
}
@Override
public String getName() {
return title;
}
@Override
public String getSearchText() {
return title;

3
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
public class Device extends SearchTextBased<DeviceId> {
public class Device extends SearchTextBased<DeviceId> implements HasName {
private static final long serialVersionUID = 2807343040519543363L;
@ -64,6 +64,7 @@ public class Device extends SearchTextBased<DeviceId> {
this.customerId = customerId;
}
@Override
public String getName() {
return name;
}

22
common/data/src/main/java/org/thingsboard/server/common/data/HasName.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
public interface HasName {
String getName();
}

7
common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java

@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
public class Tenant extends ContactBased<TenantId>{
public class Tenant extends ContactBased<TenantId> implements HasName {
private static final long serialVersionUID = 8057243243859922101L;
@ -50,6 +50,11 @@ public class Tenant extends ContactBased<TenantId>{
this.title = title;
}
@Override
public String getName() {
return title;
}
public String getRegion() {
return region;
}

7
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.security.Authority;
import com.fasterxml.jackson.databind.JsonNode;
public class User extends SearchTextBased<UserId> {
public class User extends SearchTextBased<UserId> implements HasName {
private static final long serialVersionUID = 8250339805336035966L;
@ -77,6 +77,11 @@ public class User extends SearchTextBased<UserId> {
this.email = email;
}
@Override
public String getName() {
return email;
}
public Authority getAuthority() {
return authority;
}

7
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -21,6 +21,7 @@ import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -30,7 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId;
@Data
@Builder
@AllArgsConstructor
public class Alarm extends BaseData<AlarmId> {
public class Alarm extends BaseData<AlarmId> implements HasName {
private TenantId tenantId;
private String type;
@ -52,4 +53,8 @@ public class Alarm extends BaseData<AlarmId> {
super(id);
}
@Override
public String getName() {
return type;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -16,12 +16,13 @@
package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
public class Asset extends SearchTextBased<AssetId> {
public class Asset extends SearchTextBased<AssetId> implements HasName {
private static final long serialVersionUID = 2807343040519543363L;
@ -64,6 +65,7 @@ public class Asset extends SearchTextBased<AssetId> {
this.customerId = customerId;
}
@Override
public String getName() {
return name;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java

@ -15,13 +15,14 @@
*/
package org.thingsboard.server.common.data.plugin;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import com.fasterxml.jackson.databind.JsonNode;
public class PluginMetaData extends SearchTextBased<PluginId> {
public class PluginMetaData extends SearchTextBased<PluginId> implements HasName {
private static final long serialVersionUID = 1L;
@ -75,6 +76,7 @@ public class PluginMetaData extends SearchTextBased<PluginId> {
this.tenantId = tenantId;
}
@Override
public String getName() {
return name;
}

10
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java

@ -47,11 +47,11 @@ public class EntityRelation {
this.additionalInfo = additionalInfo;
}
public EntityRelation(EntityRelation device) {
this.from = device.getFrom();
this.to = device.getTo();
this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo();
public EntityRelation(EntityRelation entityRelation) {
this.from = entityRelation.getFrom();
this.to = entityRelation.getTo();
this.type = entityRelation.getType();
this.additionalInfo = entityRelation.getAdditionalInfo();
}
public EntityId getFrom() {

59
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.relation;
public class EntityRelationInfo extends EntityRelation {
private static final long serialVersionUID = 2807343097519543363L;
private String toName;
public EntityRelationInfo() {
super();
}
public EntityRelationInfo(EntityRelation entityRelation) {
super(entityRelation);
}
public String getToName() {
return toName;
}
public void setToName(String toName) {
this.toName = toName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
EntityRelationInfo that = (EntityRelationInfo) o;
return toName != null ? toName.equals(that.toName) : that.toName == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (toName != null ? toName.hashCode() : 0);
return result;
}
}

8
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
import lombok.Data;
import lombok.ToString;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.RuleId;
@ -26,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
@Data
public class RuleMetaData extends SearchTextBased<RuleId> {
public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
private static final long serialVersionUID = -5656679015122935465L;
@ -66,4 +67,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> {
return name;
}
@Override
public String getName() {
return name;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -32,7 +32,7 @@ public interface AlarmService {
ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
ListenableFuture<Alarm> findAlarmById(AlarmId alarmId);
ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);

5
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.*;
@ -54,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
public class BaseAlarmService extends BaseEntityService implements AlarmService {
public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public static final String ALARM_RELATION_PREFIX = "ALARM_";
public static final String ALARM_RELATION = "ALARM_ANY";
@ -190,7 +191,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
}
@Override
public ListenableFuture<Alarm> findAlarmById(AlarmId alarmId) {
public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) {
log.trace("Executing findAlarmById [{}]", alarmId);
validateId(alarmId, "Incorrect alarmId " + alarmId);
return alarmDao.findAlarmByIdAsync(alarmId.getId());

4
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
@ -55,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
public class BaseAssetService extends BaseEntityService implements AssetService {
public class BaseAssetService extends AbstractEntityService implements AssetService {
@Autowired
private AssetDao assetDao;

1
dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java

@ -45,7 +45,6 @@ public class ServiceCacheConfiguration {
@Value("${cache.device_credentials.time_to_live}")
private Integer cacheDeviceCredentialsTTL;
@Value("${zk.enabled}")
private boolean zkEnabled;
@Value("${zk.url}")

6
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -31,17 +31,15 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.service.DataValidator;
@ -53,7 +51,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.service.Validator;
@Service
@Slf4j
public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
public class CustomerServiceImpl extends AbstractEntityService implements CustomerService {
private static final String PUBLIC_CUSTOMER_TITLE = "Public";

5
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId;
@ -27,8 +28,12 @@ public interface DashboardService {
public Dashboard findDashboardById(DashboardId dashboardId);
public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId);
public DashboardInfo findDashboardInfoById(DashboardId dashboardId);
public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId);
public Dashboard saveDashboard(Dashboard dashboard);
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);

24
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -17,9 +17,13 @@ package org.thingsboard.server.dao.dashboard;
import static org.thingsboard.server.dao.DaoUtil.convertDataList;
import static org.thingsboard.server.dao.DaoUtil.getData;
import static org.thingsboard.server.dao.service.Validator.validateId;
import java.util.List;
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.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Dashboard;
@ -30,7 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.service.DataValidator;
@ -42,7 +46,7 @@ import org.thingsboard.server.dao.service.Validator;
@Service
@Slf4j
public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
public class DashboardServiceImpl extends AbstractEntityService implements DashboardService {
@Autowired
private DashboardDao dashboardDao;
@ -64,6 +68,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
return getData(dashboardEntity);
}
@Override
public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId) {
log.trace("Executing findDashboardByIdAsync [{}]", dashboardId);
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
ListenableFuture<DashboardEntity> dashboardEntity = dashboardDao.findByIdAsync(dashboardId.getId());
return Futures.transform(dashboardEntity, (Function<? super DashboardEntity, ? extends Dashboard>) input -> getData(input));
}
@Override
public DashboardInfo findDashboardInfoById(DashboardId dashboardId) {
log.trace("Executing findDashboardInfoById [{}]", dashboardId);
@ -72,6 +84,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
return getData(dashboardInfoEntity);
}
@Override
public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId) {
log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId);
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
ListenableFuture<DashboardInfoEntity> dashboardInfoEntity = dashboardInfoDao.findByIdAsync(dashboardId.getId());
return Futures.transform(dashboardInfoEntity, (Function<? super DashboardInfoEntity, ? extends DashboardInfo>) input -> getData(input));
}
@Override
public Dashboard saveDashboard(Dashboard dashboard) {
log.trace("Executing saveDashboard [{}]", dashboard);

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.DeviceEntity;
@ -58,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
public class DeviceServiceImpl extends BaseEntityService implements DeviceService {
public class DeviceServiceImpl extends AbstractEntityService implements DeviceService {
@Autowired
private DeviceDao deviceDao;

36
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.relation.RelationService;
@Slf4j
public abstract class AbstractEntityService {
@Autowired
protected RelationService relationService;
protected void deleteEntityRelations(EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId);
relationService.deleteEntityRelations(entityId);
}
}

93
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -15,23 +15,102 @@
*/
package org.thingsboard.server.dao.entity;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.relation.RelationService;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
/**
* Created by ashvayka on 04.05.17.
*/
@Service
@Slf4j
public class BaseEntityService {
public class BaseEntityService extends AbstractEntityService implements EntityService {
@Autowired
protected RelationService relationService;
private AssetService assetService;
protected void deleteEntityRelations(EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId);
relationService.deleteEntityRelations(entityId);
@Autowired
private DeviceService deviceService;
@Autowired
private RuleService ruleService;
@Autowired
private PluginService pluginService;
@Autowired
private TenantService tenantService;
@Autowired
private CustomerService customerService;
@Autowired
private UserService userService;
@Autowired
private DashboardService dashboardService;
@Autowired
private AlarmService alarmService;
@Override
public void deleteEntityRelations(EntityId entityId) {
super.deleteEntityRelations(entityId);
}
@Override
public ListenableFuture<String> fetchEntityNameAsync(EntityId entityId) {
log.trace("Executing fetchEntityNameAsync [{}]", entityId);
ListenableFuture<String> entityName;
ListenableFuture<? extends HasName> hasName;
switch (entityId.getEntityType()) {
case ASSET:
hasName = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
break;
case DEVICE:
hasName = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
break;
case RULE:
hasName = ruleService.findRuleByIdAsync(new RuleId(entityId.getId()));
break;
case PLUGIN:
hasName = pluginService.findPluginByIdAsync(new PluginId(entityId.getId()));
break;
case TENANT:
hasName = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
break;
case CUSTOMER:
hasName = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
break;
case USER:
hasName = userService.findUserByIdAsync(new UserId(entityId.getId()));
break;
case DASHBOARD:
hasName = dashboardService.findDashboardInfoByIdAsync(new DashboardId(entityId.getId()));
break;
case ALARM:
hasName = alarmService.findAlarmByIdAsync(new AlarmId(entityId.getId()));
break;
default:
throw new IllegalStateException("Not Implemented!");
}
entityName = Futures.transform(hasName, (Function<HasName, String>) hasName1 -> hasName1.getName() );
return entityName;
}
}

28
dao/src/main/java/org/thingsboard/server/dao/entity/EntityService.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
public interface EntityService {
ListenableFuture<String> fetchEntityNameAsync(EntityId entityId);
void deleteEntityRelations(EntityId entityId);
}

6
dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java

@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
@ -30,9 +29,8 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -55,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class BasePluginService extends BaseEntityService implements PluginService {
public class BasePluginService extends AbstractEntityService implements PluginService {
//TODO: move to a better place.
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);

43
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -23,9 +23,24 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import javax.annotation.Nullable;
import java.util.*;
@ -41,6 +56,9 @@ public class BaseRelationService implements RelationService {
@Autowired
private RelationDao relationDao;
@Autowired
private EntityService entityService;
@Override
public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType);
@ -99,6 +117,31 @@ public class BaseRelationService implements RelationService {
return relationDao.findAllByFrom(from);
}
@Override
public ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from) {
log.trace("Executing findInfoByFrom [{}]", from);
validate(from);
ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByFrom(from);
ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
(AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation)));
return Futures.successfulAsList(futures);
});
return relationsInfo;
}
private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation) {
ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(relation.getTo());
ListenableFuture<EntityRelationInfo> entityRelationInfo =
Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
entityRelationInfo1.setToName(entityName1);
return entityRelationInfo1;
});
return entityRelationInfo;
}
@Override
public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
log.trace("Executing findByFromAndType [{}][{}]", from, relationType);

3
dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import java.util.List;
@ -38,6 +39,8 @@ public interface RelationService {
ListenableFuture<List<EntityRelation>> findByFrom(EntityId from);
ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from);
ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
ListenableFuture<List<EntityRelation>> findByTo(EntityId to);

6
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java

@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@ -34,11 +33,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.AssetEntity;
import org.thingsboard.server.dao.model.RuleMetaDataEntity;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.service.DataValidator;
@ -58,7 +56,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
public class BaseRuleService extends BaseEntityService implements RuleService {
public class BaseRuleService extends AbstractEntityService implements RuleService {
private final TenantId systemTenantId = new TenantId(NULL_UUID);

6
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -26,7 +26,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
@ -34,9 +33,8 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.rule.RuleService;
@ -50,7 +48,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
@Service
@Slf4j
public class TenantServiceImpl extends BaseEntityService implements TenantService {
public class TenantServiceImpl extends AbstractEntityService implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global";

3
dao/src/main/java/org/thingsboard/server/dao/user/UserService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.user;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -27,6 +28,8 @@ public interface UserService {
public User findUserById(UserId userId);
public ListenableFuture<User> findUserByIdAsync(UserId userId);
public User findUserByEmail(String email);
public User saveUser(User user);

15
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -23,6 +23,9 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
import java.util.List;
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.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@ -35,7 +38,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.*;
@ -47,7 +50,7 @@ import org.springframework.stereotype.Service;
@Service
@Slf4j
public class UserServiceImpl extends BaseEntityService implements UserService {
public class UserServiceImpl extends AbstractEntityService implements UserService {
@Autowired
private UserDao userDao;
@ -77,6 +80,14 @@ public class UserServiceImpl extends BaseEntityService implements UserService {
return getData(userEntity);
}
@Override
public ListenableFuture<User> findUserByIdAsync(UserId userId) {
log.trace("Executing findUserByIdAsync [{}]", userId);
validateId(userId, "Incorrect userId " + userId);
ListenableFuture<UserEntity> userEntity = userDao.findByIdAsync(userId.getId());
return Futures.transform(userEntity, (Function<? super UserEntity, ? extends User>) input -> getData(input));
}
@Override
public User saveUser(User user) {
log.trace("Executing saveUser [{}]", user);

6
dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java

@ -95,7 +95,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(0L, created.getAckTs());
Assert.assertEquals(0L, created.getClearTs());
Alarm fetched = alarmService.findAlarmById(created.getId()).get();
Alarm fetched = alarmService.findAlarmByIdAsync(created.getId()).get();
Assert.assertEquals(created, fetched);
}
@ -137,7 +137,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(created, alarms.getData().get(0));
alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmById(created.getId()).get();
created = alarmService.findAlarmByIdAsync(created.getId()).get();
alarms = alarmService.findAlarms(AlarmQuery.builder()
.affectedEntityId(childId)
@ -158,7 +158,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, alarms.getData().size());
alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get();
created = alarmService.findAlarmById(created.getId()).get();
created = alarmService.findAlarmByIdAsync(created.getId()).get();
alarms = alarmService.findAlarms(AlarmQuery.builder()
.affectedEntityId(childId)

6
docker/.env

@ -1 +1,7 @@
CASSANDRA_DATA_DIR=/home/docker/cassandra_volume
# cassandra schema container environment variables
CREATE_SCHEMA=true
ADD_SYSTEM_DATA=false
ADD_DEMO_DATA=false
CASSANDRA_URL=cassandra

132
docker/cassandra/cassandra.yaml

@ -0,0 +1,132 @@
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
apiVersion: v1
kind: Service
metadata:
name: cassandra-headless
labels:
app: cassandra-headless
spec:
ports:
- port: 9042
name: cql
clusterIP: None
selector:
app: cassandra
---
apiVersion: "apps/v1beta1"
kind: StatefulSet
metadata:
name: cassandra
spec:
serviceName: cassandra-headless
replicas: 2
template:
metadata:
labels:
app: cassandra
spec:
nodeSelector:
machinetype: other
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- cassandra-headless
topologyKey: "kubernetes.io/hostname"
containers:
- name: cassandra
image: cassandra:3.9
imagePullPolicy: Always
ports:
- containerPort: 7000
name: intra-node
- containerPort: 7001
name: tls-intra-node
- containerPort: 7199
name: jmx
- containerPort: 9042
name: cql
- containerPort: 9160
name: thrift
securityContext:
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "PID=$(pidof java) && kill $PID && while ps -p $PID > /dev/null; do sleep 1; done"]
env:
- name: MAX_HEAP_SIZE
value: 2048M
- name: HEAP_NEWSIZE
value: 100M
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra-headless.default.svc.cluster.local"
- name: CASSANDRA_CLUSTER_NAME
value: "Thingsboard-Cluster"
- name: CASSANDRA_DC
value: "DC1-Thingsboard-Cluster"
- name: CASSANDRA_RACK
value: "Rack-Thingsboard-Cluster"
- name: CASSANDRA_AUTO_BOOTSTRAP
value: "false"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /ready-probe.sh
initialDelaySeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: cassandra-data
mountPath: /var/lib/cassandra/data
- name: cassandra-commitlog
mountPath: /var/lib/cassandra/commitlog
volumeClaimTemplates:
- metadata:
name: cassandra-data
annotations:
volume.beta.kubernetes.io/storage-class: fast
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 3Gi
- metadata:
name: cassandra-commitlog
annotations:
volume.beta.kubernetes.io/storage-class: fast
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 2Gi

32
docker/deploy.sh → docker/common/common.yaml

@ -1,4 +1,3 @@
#!/bin/bash
#
# Copyright © 2016-2017 The Thingsboard Authors
#
@ -15,17 +14,20 @@
# limitations under the License.
#
command='docker-compose -f docker-compose.yml -f docker-compose.random.yml'
echo "stopping images.."
$command stop
echo "removing stopped images.."
$command rm -f
echo "building images.."
$command build
echo "starting images..."
$command up -d
---
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: slow
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
---
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
---

2
docker/docker-compose.static.yml

@ -17,7 +17,7 @@
version: '2'
services:
db:
cassandra:
ports:
- "9042:9042"
- "9160:9160"

28
docker/docker-compose.yml

@ -17,24 +17,32 @@
version: '2'
services:
thingsboard:
image: "thingsboard/application:1.2.3"
tb:
image: "thingsboard/application:1.2.4"
ports:
- "8080:8080"
- "1883:1883"
- "5683:5683/udp"
env_file:
- thingsboard.env
entrypoint: ./run_thingsboard.sh
thingsboard-db-schema:
image: "thingsboard/thingsboard-db-schema:1.2.3"
env_file:
- thingsboard-db-schema.env
entrypoint: ./install_schema.sh
db:
- tb.env
entrypoint: ./run-application.sh
tb-cassandra-schema:
image: "thingsboard/tb-cassandra-schema:1.2.4"
environment:
- CREATE_SCHEMA=${CREATE_SCHEMA}
- ADD_SYSTEM_DATA=${ADD_SYSTEM_DATA}
- ADD_DEMO_DATA=${ADD_DEMO_DATA}
- CASSANDRA_URL=${CASSANDRA_URL}
entrypoint: ./install-schema.sh
cassandra:
image: "cassandra:3.9"
ports:
- "9042"
- "9160"
volumes:
- "${CASSANDRA_DATA_DIR}:/var/lib/cassandra"
zk:
image: "zookeeper:3.4.9"
ports:
- "2181"
restart: always

4
docker/thingsboard-db-schema/Dockerfile → docker/tb-cassandra-schema/Dockerfile

@ -16,12 +16,12 @@
FROM cassandra:3.9
ADD install_schema.sh /root/install_schema.sh
ADD install-schema.sh /root/install-schema.sh
RUN apt-get update \
&& apt-get install -y nmap
RUN chmod +x /root/install_schema.sh
RUN chmod +x /root/install-schema.sh
ADD schema.cql /root/schema.cql
ADD demo-data.cql /root/demo-data.cql

13
docker/tb-cassandra-schema/Makefile

@ -0,0 +1,13 @@
VERSION=1.2.4
PROJECT=thingsboard
APP=tb-cassandra-schema
build:
cp ../../dao/src/main/resources/schema.cql .
cp ../../dao/src/main/resources/demo-data.cql .
cp ../../dao/src/main/resources/system-data.cql .
docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
rm schema.cql demo-data.cql system-data.cql
push: build
docker push ${PROJECT}/${APP}:${VERSION}

16
docker/thingsboard-db-schema/install_schema.sh → docker/tb-cassandra-schema/install-schema.sh

@ -16,15 +16,15 @@
#
until nmap db -p 9042 | grep "9042/tcp open"
until nmap $CASSANDRA_URL -p 9042 | grep "9042/tcp open"
do
echo "Wait for Cassandra..."
echo "Wait for $CASSANDRA_URL..."
sleep 10
done
if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then
if [ "$CREATE_SCHEMA" == "true" ]; then
echo "Creating 'Thingsboard' keyspace..."
cqlsh db -f /root/schema.cql
cqlsh $CASSANDRA_URL -f /root/schema.cql
if [ "$?" -eq 0 ]; then
echo "'Thingsboard' keyspace was successfully created!"
else
@ -32,9 +32,9 @@ if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then
fi
fi
if [ "$SKIP_SYSTEM_DATA" == "false" ]; then
if [ "$ADD_SYSTEM_DATA" == "true" ]; then
echo "Adding system data..."
cqlsh db -f /root/system-data.cql
cqlsh $CASSANDRA_URL -f /root/system-data.cql
if [ "$?" -eq 0 ]; then
echo "System data was successfully added!"
else
@ -42,9 +42,9 @@ if [ "$SKIP_SYSTEM_DATA" == "false" ]; then
fi
fi
if [ "$SKIP_DEMO_DATA" == "false" ]; then
if [ "$ADD_DEMO_DATA" == "true" ]; then
echo "Adding demo data..."
cqlsh db -f /root/demo-data.cql
cqlsh $CASSANDRA_URL -f /root/demo-data.cql
if [ "$?" -eq 0 ]; then
echo "Demo data was successfully added!"
else

33
docker/docker-compose.random.yml → docker/tb-cassandra-schema/tb-cassandra-schema.yaml

@ -14,13 +14,26 @@
# limitations under the License.
#
version: '2'
services:
db:
ports:
- "9042"
- "9160"
zk:
ports:
- "2181"
apiVersion: v1
kind: Pod
metadata:
name: tb-cassandra-schema
spec:
containers:
- name: tb-cassandra-schema
imagePullPolicy: Always
image: thingsboard/tb-cassandra-schema:1.2.4
env:
- name: CREATE_SCHEMA
value: "false"
- name: ADD_SYSTEM_DATA
value: "false"
- name : ADD_DEMO_DATA
value: "false"
- name : CASSANDRA_URL
value: "cassandra-headless"
command:
- sh
- -c
- ./install-schema.sh
restartPolicy: Never

3
docker/thingsboard.env → docker/tb.env

@ -1,6 +1,7 @@
#Thingsboard server configuration
CASSANDRA_URL=db:9042
TB_CASSANDRA_SCHEMA_URL=tb-cassandra-schema
CASSANDRA_URL=cassandra:9042
ZOOKEEPER_URL=zk:2181
MQTT_BIND_ADDRESS=0.0.0.0
MQTT_BIND_PORT=1883

4
docker/thingsboard/Dockerfile → docker/tb/Dockerfile

@ -16,9 +16,9 @@
FROM openjdk:8-jre
ADD run_thingsboard.sh /root/run_thingsboard.sh
ADD run-application.sh /root/run-application.sh
ADD thingsboard.deb /root/thingsboard.deb
RUN chmod +x /root/run_thingsboard.sh
RUN chmod +x /root/run-application.sh
WORKDIR /root

11
docker/tb/Makefile

@ -0,0 +1,11 @@
VERSION=1.2.4
PROJECT=thingsboard
APP=application
build:
cp ../../application/target/thingsboard.deb .
docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
rm thingsboard.deb
push: build
docker push ${PROJECT}/${APP}:${VERSION}

6
docker/thingsboard/run_thingsboard.sh → docker/tb/run-application.sh

@ -21,12 +21,12 @@ dpkg -i /root/thingsboard.deb
reachable=0
while [ $reachable -eq 0 ];
do
echo "thingsboard-db-schema container is still in progress. waiting until it completed..."
echo "$TB_CASSANDRA_SCHEMA_URL container is still in progress. waiting until it completed..."
sleep 3
ping -q -c 1 thingsboard-db-schema > /dev/null 2>&1
ping -q -c 1 $TB_CASSANDRA_SCHEMA_URL > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
echo "thingsboard-db-schema container completed!"
echo "$TB_CASSANDRA_SCHEMA_URL container completed!"
reachable=1
fi
done

121
docker/tb/tb.yaml

@ -0,0 +1,121 @@
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
---
apiVersion: v1
kind: Service
metadata:
name: tb-service
labels:
app: tb-service
spec:
ports:
- port: 8080
name: ui
- port: 1883
name: mqtt
- port: 5683
name: coap
selector:
app: tb
type: LoadBalancer
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: tb-budget
spec:
selector:
matchLabels:
app: tb
minAvailable: 3
---
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-config
data:
zookeeper.enabled: "true"
zookeeper.url: "zk-headless"
cassandra.url: "cassandra-headless:9042"
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: tb
spec:
serviceName: "tb-service"
replicas: 3
template:
metadata:
labels:
app: tb
spec:
nodeSelector:
machinetype: tb
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- tb-service
topologyKey: "kubernetes.io/hostname"
containers:
- name: tb
imagePullPolicy: Always
image: thingsboard/application:1.2.4
ports:
- containerPort: 8080
name: ui
- containerPort: 1883
name: mqtt
- containerPort: 5683
name: coap
- containerPort: 9001
name: rpc
env:
- name: ZOOKEEPER_ENABLED
valueFrom:
configMapKeyRef:
name: tb-config
key: zookeeper.enabled
- name: ZOOKEEPER_URL
valueFrom:
configMapKeyRef:
name: tb-config
key: zookeeper.url
- name : CASSANDRA_URL
valueFrom:
configMapKeyRef:
name: tb-config
key: cassandra.url
- name : RPC_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
command:
- sh
- -c
- ./run-application.sh
livenessProbe:
httpGet:
path: /login
port: ui-port
initialDelaySeconds: 120
timeoutSeconds: 10

5
docker/thingsboard-db-schema.env

@ -1,5 +0,0 @@
#Db schema configuration
SKIP_SCHEMA_CREATION=false
SKIP_SYSTEM_DATA=false
SKIP_DEMO_DATA=false

28
docker/thingsboard-db-schema/build_and_deploy.sh

@ -1,28 +0,0 @@
#!/bin/bash
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
cp ../../dao/src/main/resources/schema.cql schema.cql
cp ../../dao/src/main/resources/demo-data.cql demo-data.cql
cp ../../dao/src/main/resources/system-data.cql system-data.cql
docker build -t thingsboard/thingsboard-db-schema:1.2.3 -t thingsboard/thingsboard-db-schema:latest .
docker login
docker push thingsboard/thingsboard-db-schema:1.2.3
docker push thingsboard/thingsboard-db-schema:latest

26
docker/thingsboard/build_and_deploy.sh

@ -1,26 +0,0 @@
#!/bin/bash
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
cp ../../application/target/thingsboard.deb thingsboard.deb
docker build -t thingsboard/application:1.2.3 -t thingsboard/application:latest .
docker login
docker push thingsboard/application:1.2.3
docker push thingsboard/application:latest

71
docker/zookeeper/Dockerfile

@ -0,0 +1,71 @@
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
FROM ubuntu:16.04
ENV ZK_USER=zookeeper \
ZK_DATA_DIR=/var/lib/zookeeper/data \
ZK_DATA_LOG_DIR=/var/lib/zookeeper/log \
ZK_LOG_DIR=/var/log/zookeeper \
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ARG GPG_KEY=C823E3E5B12AF29C67F81976F5CECB3CB5E9BD2D
ARG ZK_DIST=zookeeper-3.4.9
RUN set -x \
&& apt-get update \
&& apt-get install -y openjdk-8-jre-headless wget netcat-openbsd \
&& wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz" \
&& wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz.asc" \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-key "$GPG_KEY" \
&& gpg --batch --verify "$ZK_DIST.tar.gz.asc" "$ZK_DIST.tar.gz" \
&& tar -xzf "$ZK_DIST.tar.gz" -C /opt \
&& rm -r "$GNUPGHOME" "$ZK_DIST.tar.gz" "$ZK_DIST.tar.gz.asc" \
&& ln -s /opt/$ZK_DIST /opt/zookeeper \
&& rm -rf /opt/zookeeper/CHANGES.txt \
/opt/zookeeper/README.txt \
/opt/zookeeper/NOTICE.txt \
/opt/zookeeper/CHANGES.txt \
/opt/zookeeper/README_packaging.txt \
/opt/zookeeper/build.xml \
/opt/zookeeper/config \
/opt/zookeeper/contrib \
/opt/zookeeper/dist-maven \
/opt/zookeeper/docs \
/opt/zookeeper/ivy.xml \
/opt/zookeeper/ivysettings.xml \
/opt/zookeeper/recipes \
/opt/zookeeper/src \
/opt/zookeeper/$ZK_DIST.jar.asc \
/opt/zookeeper/$ZK_DIST.jar.md5 \
/opt/zookeeper/$ZK_DIST.jar.sha1 \
&& apt-get autoremove -y wget \
&& rm -rf /var/lib/apt/lists/*
#Copy configuration generator script to bin
COPY zk-gen-config.sh zk-ok.sh /opt/zookeeper/bin/
# Create a user for the zookeeper process and configure file system ownership
# for nessecary directories and symlink the distribution as a user executable
RUN set -x \
&& useradd $ZK_USER \
&& [ `id -u $ZK_USER` -eq 1000 ] \
&& [ `id -g $ZK_USER` -eq 1000 ] \
&& mkdir -p $ZK_DATA_DIR $ZK_DATA_LOG_DIR $ZK_LOG_DIR /usr/share/zookeeper /tmp/zookeeper /usr/etc/ \
&& chown -R "$ZK_USER:$ZK_USER" /opt/$ZK_DIST $ZK_DATA_DIR $ZK_LOG_DIR $ZK_DATA_LOG_DIR /tmp/zookeeper \
&& ln -s /opt/zookeeper/conf/ /usr/etc/zookeeper \
&& ln -s /opt/zookeeper/bin/* /usr/bin \
&& ln -s /opt/zookeeper/$ZK_DIST.jar /usr/share/zookeeper/ \
&& ln -s /opt/zookeeper/lib/* /usr/share/zookeeper

9
docker/zookeeper/Makefile

@ -0,0 +1,9 @@
VERSION=1.2.4
PROJECT=thingsboard
APP=zk
build:
docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
push: build
docker push ${PROJECT}/${APP}:${VERSION}

153
docker/zookeeper/zk-gen-config.sh

@ -0,0 +1,153 @@
#!/usr/bin/env bash
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
ZK_USER=${ZK_USER:-"zookeeper"}
ZK_LOG_LEVEL=${ZK_LOG_LEVEL:-"INFO"}
ZK_DATA_DIR=${ZK_DATA_DIR:-"/var/lib/zookeeper/data"}
ZK_DATA_LOG_DIR=${ZK_DATA_LOG_DIR:-"/var/lib/zookeeper/log"}
ZK_LOG_DIR=${ZK_LOG_DIR:-"var/log/zookeeper"}
ZK_CONF_DIR=${ZK_CONF_DIR:-"/opt/zookeeper/conf"}
ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
ZK_SERVER_PORT=${ZK_SERVER_PORT:-2888}
ZK_ELECTION_PORT=${ZK_ELECTION_PORT:-3888}
ZK_TICK_TIME=${ZK_TICK_TIME:-2000}
ZK_INIT_LIMIT=${ZK_INIT_LIMIT:-10}
ZK_SYNC_LIMIT=${ZK_SYNC_LIMIT:-5}
ZK_HEAP_SIZE=${ZK_HEAP_SIZE:-2G}
ZK_MAX_CLIENT_CNXNS=${ZK_MAX_CLIENT_CNXNS:-60}
ZK_MIN_SESSION_TIMEOUT=${ZK_MIN_SESSION_TIMEOUT:- $((ZK_TICK_TIME*2))}
ZK_MAX_SESSION_TIMEOUT=${ZK_MAX_SESSION_TIMEOUT:- $((ZK_TICK_TIME*20))}
ZK_SNAP_RETAIN_COUNT=${ZK_SNAP_RETAIN_COUNT:-3}
ZK_PURGE_INTERVAL=${ZK_PURGE_INTERVAL:-0}
ID_FILE="$ZK_DATA_DIR/myid"
ZK_CONFIG_FILE="$ZK_CONF_DIR/zoo.cfg"
LOGGER_PROPS_FILE="$ZK_CONF_DIR/log4j.properties"
JAVA_ENV_FILE="$ZK_CONF_DIR/java.env"
HOST=`hostname -s`
DOMAIN=`hostname -d`
function print_servers() {
for (( i=1; i<=$ZK_REPLICAS; i++ ))
do
echo "server.$i=$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT"
done
}
function validate_env() {
echo "Validating environment"
if [ -z $ZK_REPLICAS ]; then
echo "ZK_REPLICAS is a mandatory environment variable"
exit 1
fi
if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then
NAME=${BASH_REMATCH[1]}
ORD=${BASH_REMATCH[2]}
else
echo "Failed to extract ordinal from hostname $HOST"
exit 1
fi
MY_ID=$((ORD+1))
echo "ZK_REPLICAS=$ZK_REPLICAS"
echo "MY_ID=$MY_ID"
echo "ZK_LOG_LEVEL=$ZK_LOG_LEVEL"
echo "ZK_DATA_DIR=$ZK_DATA_DIR"
echo "ZK_DATA_LOG_DIR=$ZK_DATA_LOG_DIR"
echo "ZK_LOG_DIR=$ZK_LOG_DIR"
echo "ZK_CLIENT_PORT=$ZK_CLIENT_PORT"
echo "ZK_SERVER_PORT=$ZK_SERVER_PORT"
echo "ZK_ELECTION_PORT=$ZK_ELECTION_PORT"
echo "ZK_TICK_TIME=$ZK_TICK_TIME"
echo "ZK_INIT_LIMIT=$ZK_INIT_LIMIT"
echo "ZK_SYNC_LIMIT=$ZK_SYNC_LIMIT"
echo "ZK_MAX_CLIENT_CNXNS=$ZK_MAX_CLIENT_CNXNS"
echo "ZK_MIN_SESSION_TIMEOUT=$ZK_MIN_SESSION_TIMEOUT"
echo "ZK_MAX_SESSION_TIMEOUT=$ZK_MAX_SESSION_TIMEOUT"
echo "ZK_HEAP_SIZE=$ZK_HEAP_SIZE"
echo "ZK_SNAP_RETAIN_COUNT=$ZK_SNAP_RETAIN_COUNT"
echo "ZK_PURGE_INTERVAL=$ZK_PURGE_INTERVAL"
echo "ENSEMBLE"
print_servers
echo "Environment validation successful"
}
function create_config() {
rm -f $ZK_CONFIG_FILE
echo "Creating ZooKeeper configuration"
echo "#This file was autogenerated by zk DO NOT EDIT" >> $ZK_CONFIG_FILE
echo "clientPort=$ZK_CLIENT_PORT" >> $ZK_CONFIG_FILE
echo "dataDir=$ZK_DATA_DIR" >> $ZK_CONFIG_FILE
echo "dataLogDir=$ZK_DATA_LOG_DIR" >> $ZK_CONFIG_FILE
echo "tickTime=$ZK_TICK_TIME" >> $ZK_CONFIG_FILE
echo "initLimit=$ZK_INIT_LIMIT" >> $ZK_CONFIG_FILE
echo "syncLimit=$ZK_SYNC_LIMIT" >> $ZK_CONFIG_FILE
echo "maxClientCnxns=$ZK_MAX_CLIENT_CNXNS" >> $ZK_CONFIG_FILE
echo "minSessionTimeout=$ZK_MIN_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
echo "maxSessionTimeout=$ZK_MAX_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
echo "autopurge.snapRetainCount=$ZK_SNAP_RETAIN_COUNT" >> $ZK_CONFIG_FILE
echo "autopurge.purgeInteval=$ZK_PURGE_INTERVAL" >> $ZK_CONFIG_FILE
if [ $ZK_REPLICAS -gt 1 ]; then
print_servers >> $ZK_CONFIG_FILE
fi
echo "Wrote ZooKeeper configuration file to $ZK_CONFIG_FILE"
}
function create_data_dirs() {
echo "Creating ZooKeeper data directories and setting permissions"
if [ ! -d $ZK_DATA_DIR ]; then
mkdir -p $ZK_DATA_DIR
chown -R $ZK_USER:$ZK_USER $ZK_DATA_DIR
fi
if [ ! -d $ZK_DATA_LOG_DIR ]; then
mkdir -p $ZK_DATA_LOG_DIR
chown -R $ZK_USER:$ZK_USER $ZK_DATA_LOG_DIR
fi
if [ ! -d $ZK_LOG_DIR ]; then
mkdir -p $ZK_LOG_DIR
chown -R $ZK_USER:$ZK_USER $ZK_LOG_DIR
fi
if [ ! -f $ID_FILE ]; then
echo $MY_ID >> $ID_FILE
fi
echo "Created ZooKeeper data directories and set permissions in $ZK_DATA_DIR"
}
function create_log_props () {
rm -f $LOGGER_PROPS_FILE
echo "Creating ZooKeeper log4j configuration"
echo "zookeeper.root.logger=CONSOLE" >> $LOGGER_PROPS_FILE
echo "zookeeper.console.threshold="$ZK_LOG_LEVEL >> $LOGGER_PROPS_FILE
echo "log4j.rootLogger=\${zookeeper.root.logger}" >> $LOGGER_PROPS_FILE
echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender" >> $LOGGER_PROPS_FILE
echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}" >> $LOGGER_PROPS_FILE
echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout" >> $LOGGER_PROPS_FILE
echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n" >> $LOGGER_PROPS_FILE
echo "Wrote log4j configuration to $LOGGER_PROPS_FILE"
}
function create_java_env() {
rm -f $JAVA_ENV_FILE
echo "Creating JVM configuration file"
echo "ZOO_LOG_DIR=$ZK_LOG_DIR" >> $JAVA_ENV_FILE
echo "JVMFLAGS=\"-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE\"" >> $JAVA_ENV_FILE
echo "Wrote JVM configuration to $JAVA_ENV_FILE"
}
validate_env && create_config && create_log_props && create_data_dirs && create_java_env

25
docker/deploy_cassandra_zookeeper.sh → docker/zookeeper/zk-ok.sh

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
#
# Copyright © 2016-2017 The Thingsboard Authors
#
@ -15,17 +15,14 @@
# limitations under the License.
#
# zk-ok.sh uses the ruok ZooKeeper four letter work to determine if the instance
# is health. The $? variable will be set to 0 if server responds that it is
# healthy, or 1 if the server fails to respond.
command='docker-compose -f docker-compose.yml -f docker-compose.static.yml'
echo "stopping images.."
$command stop
echo "removing stopped images.."
$command rm -f
echo "building images.."
$command build
echo "starting cassandra, zookeeper, thingsboard-db-schema images..."
$command up -d db zk thingsboard-db-schema
ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
OK=$(echo ruok | nc 127.0.0.1 $ZK_CLIENT_PORT)
if [ "$OK" == "imok" ]; then
exit 0
else
exit 1
fi

190
docker/zookeeper/zookeeper.yaml

@ -0,0 +1,190 @@
#
# Copyright © 2016-2017 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
apiVersion: v1
kind: Service
metadata:
name: zk-headless
labels:
app: zk-headless
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zk
---
apiVersion: v1
kind: ConfigMap
metadata:
name: zk-config
data:
ensemble: "zk-0;zk-1;zk-2"
replicas: "3"
jvm.heap: "500m"
tick: "2000"
init: "10"
sync: "5"
client.cnxns: "60"
snap.retain: "3"
purge.interval: "1"
client.port: "2181"
server.port: "2888"
election.port: "3888"
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-budget
spec:
selector:
matchLabels:
app: zk
minAvailable: 3
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: zk
spec:
serviceName: zk-headless
replicas: 3
template:
metadata:
labels:
app: zk
annotations:
pod.alpha.kubernetes.io/initialized: "true"
spec:
nodeSelector:
machinetype: other
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk-headless
topologyKey: "kubernetes.io/hostname"
containers:
- name: zk
imagePullPolicy: Always
image: thingsboard/zk:1.2.4
ports:
- containerPort: 2181
name: client
- containerPort: 2888
name: server
- containerPort: 3888
name: leader-election
env:
- name : ZK_ENSEMBLE
valueFrom:
configMapKeyRef:
name: zk-config
key: ensemble
- name : ZK_REPLICAS
valueFrom:
configMapKeyRef:
name: zk-config
key: replicas
- name : ZK_HEAP_SIZE
valueFrom:
configMapKeyRef:
name: zk-config
key: jvm.heap
- name : ZK_TICK_TIME
valueFrom:
configMapKeyRef:
name: zk-config
key: tick
- name : ZK_INIT_LIMIT
valueFrom:
configMapKeyRef:
name: zk-config
key: init
- name : ZK_SYNC_LIMIT
valueFrom:
configMapKeyRef:
name: zk-config
key: tick
- name : ZK_MAX_CLIENT_CNXNS
valueFrom:
configMapKeyRef:
name: zk-config
key: client.cnxns
- name: ZK_SNAP_RETAIN_COUNT
valueFrom:
configMapKeyRef:
name: zk-config
key: snap.retain
- name: ZK_PURGE_INTERVAL
valueFrom:
configMapKeyRef:
name: zk-config
key: purge.interval
- name: ZK_CLIENT_PORT
valueFrom:
configMapKeyRef:
name: zk-config
key: client.port
- name: ZK_SERVER_PORT
valueFrom:
configMapKeyRef:
name: zk-config
key: server.port
- name: ZK_ELECTION_PORT
valueFrom:
configMapKeyRef:
name: zk-config
key: election.port
command:
- sh
- -c
- zk-gen-config.sh && zkServer.sh start-foreground
readinessProbe:
exec:
command:
- "zk-ok.sh"
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
exec:
command:
- "zk-ok.sh"
initialDelaySeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: zkdatadir
mountPath: /var/lib/zookeeper
securityContext:
runAsUser: 1000
fsGroup: 1000
volumeClaimTemplates:
- metadata:
name: zkdatadir
annotations:
volume.beta.kubernetes.io/storage-class: slow
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi

2
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java

@ -41,7 +41,7 @@ import java.security.cert.X509Certificate;
*/
@Slf4j
@Component("MqttSslHandlerProvider")
@ConditionalOnProperty(prefix = "mqtt.ssl", value = "key-store", havingValue = "", matchIfMissing = false)
@ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
public class MqttSslHandlerProvider {
public static final String TLS = "TLS";

13
ui/src/app/api/entity-relation.service.js

@ -25,6 +25,7 @@ function EntityRelationService($http, $q) {
deleteRelation: deleteRelation,
deleteRelations: deleteRelations,
findByFrom: findByFrom,
findInfoByFrom: findInfoByFrom,
findByFromAndType: findByFromAndType,
findByTo: findByTo,
findByToAndType: findByToAndType,
@ -84,6 +85,18 @@ function EntityRelationService($http, $q) {
return deferred.promise;
}
function findInfoByFrom(fromId, fromType) {
var deferred = $q.defer();
var url = '/api/relations/info?fromId=' + fromId;
url += '&fromType=' + fromType;
$http.get(url, null).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function findByFromAndType(fromId, fromType, relationType) {
var deferred = $q.defer();
var url = '/api/relations?fromId=' + fromId;

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

@ -20,14 +20,13 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
.name;
/*@ngInject*/
function EntityService($http, $q, $filter, $translate, userService, deviceService,
function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
assetService, tenantService, customerService,
ruleService, pluginService, entityRelationService, attributeService, types, utils) {
ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) {
var service = {
getEntity: getEntity,
getEntities: getEntities,
getEntitiesByNameFilter: getEntitiesByNameFilter,
entityName: entityName,
processEntityAliases: processEntityAliases,
getEntityKeys: getEntityKeys,
checkEntityAlias: checkEntityAlias,
@ -63,6 +62,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
case types.entityType.plugin:
promise = pluginService.getPlugin(entityId);
break;
case types.entityType.dashboard:
promise = dashboardService.getDashboardInfo(entityId);
break;
case types.entityType.user:
promise = userService.getUser(entityId);
break;
case types.entityType.alarm:
$log.error('Get Alarm Entity is not implemented!');
break;
}
return promise;
}
@ -134,6 +142,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
case types.entityType.plugin:
promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds);
break;
case types.entityType.dashboard:
promise = getEntitiesByIdsPromise(dashboardService.getDashboardInfo, entityIds);
break;
case types.entityType.user:
promise = getEntitiesByIdsPromise(userService.getUser, entityIds);
break;
case types.entityType.alarm:
$log.error('Get Alarm Entity is not implemented!');
break;
}
return promise;
}
@ -141,34 +158,38 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
function getEntities(entityType, entityIds, config) {
var deferred = $q.defer();
var promise = getEntitiesPromise(entityType, entityIds, config);
promise.then(
function success(result) {
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
if (promise) {
promise.then(
function success(result) {
deferred.resolve(result);
},
function fail() {
deferred.reject();
}
);
} else {
deferred.reject();
}
return deferred.promise;
}
function getEntitiesByPageLinkPromise(entityType, pageLink, config) {
function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
var promise;
var user = userService.getCurrentUser();
var customerId = user.customerId;
switch (entityType) {
case types.entityType.device:
if (user.authority === 'CUSTOMER_USER') {
promise = deviceService.getCustomerDevices(customerId, pageLink, false, config);
promise = deviceService.getCustomerDevices(customerId, pageLink, false, config, subType);
} else {
promise = deviceService.getTenantDevices(pageLink, false, config);
promise = deviceService.getTenantDevices(pageLink, false, config, subType);
}
break;
case types.entityType.asset:
if (user.authority === 'CUSTOMER_USER') {
promise = assetService.getCustomerAssets(customerId, pageLink, false, config);
promise = assetService.getCustomerAssets(customerId, pageLink, false, config, subType);
} else {
promise = assetService.getTenantAssets(pageLink, false, config);
promise = assetService.getTenantAssets(pageLink, false, config, subType);
}
break;
case types.entityType.tenant:
@ -183,48 +204,48 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
case types.entityType.plugin:
promise = pluginService.getAllPlugins(pageLink);
break;
case types.entityType.dashboard:
if (user.authority === 'CUSTOMER_USER') {
promise = dashboardService.getCustomerDashboards(customerId, pageLink);
} else {
promise = dashboardService.getTenantDashboards(pageLink);
}
break;
case types.entityType.user:
$log.error('Get User Entities is not implemented!');
break;
case types.entityType.alarm:
$log.error('Get Alarm Entities is not implemented!');
break;
}
return promise;
}
function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config) {
function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
var deferred = $q.defer();
var pageLink = {limit: limit, textSearch: entityNameFilter};
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config);
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
if (promise) {
promise.then(
function success(result) {
if (result.data && result.data.length > 0) {
deferred.resolve(result.data);
} else {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
},
function fail() {
deferred.resolve(null);
}
);
return deferred.promise;
}
function entityName(entityType, entity) {
var name = '';
switch (entityType) {
case types.entityType.device:
case types.entityType.asset:
case types.entityType.rule:
case types.entityType.plugin:
name = entity.name;
break;
case types.entityType.tenant:
case types.entityType.customer:
name = entity.title;
break;
);
} else {
deferred.resolve(null);
}
return name;
return deferred.promise;
}
function entityToEntityInfo(entityType, entity) {
return { name: entityName(entityType, entity), entityType: entityType, id: entity.id.id };
return { name: entity.name, entityType: entityType, id: entity.id.id };
}
function entitiesToEntitiesInfo(entityType, entities) {

6
ui/src/app/asset/assets.tpl.html

@ -55,4 +55,10 @@
default-event-type="{{vm.types.eventType.alarm.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.asset}}">
</tb-relation-table>
</md-tab>
</tb-grid>

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

@ -98,7 +98,10 @@ export default angular.module('thingsboard.types', [])
rule: "RULE",
plugin: "PLUGIN",
tenant: "TENANT",
customer: "CUSTOMER"
customer: "CUSTOMER",
user: "USER",
dashboard: "DASHBOARD",
alarm: "ALARM"
},
entitySearchDirection: {
from: "FROM",

26
ui/src/app/common/utils.service.js

@ -108,7 +108,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
guid: guid,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
createKey: createKey
createKey: createKey,
entityTypeName: entityTypeName
}
return service;
@ -346,4 +347,27 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return dataKey;
}
function entityTypeName (type) {
switch (type) {
case types.entityType.device:
return 'entity.type-device';
case types.entityType.asset:
return 'entity.type-asset';
case types.entityType.rule:
return 'entity.type-rule';
case types.entityType.plugin:
return 'entity.type-plugin';
case types.entityType.tenant:
return 'entity.type-tenant';
case types.entityType.customer:
return 'entity.type-customer';
case types.entityType.user:
return 'entity.type-user';
case types.entityType.dashboard:
return 'entity.type-dashboard';
case types.entityType.alarm:
return 'entity.type-alarm';
}
}
}

2
ui/src/app/components/dashboard-autocomplete.tpl.html

@ -34,7 +34,7 @@
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
<span translate translate-values='{ entity: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
</div>
</md-not-found>
<div ng-messages="theForm.dashboard.$error">

2
ui/src/app/components/plugin-select.tpl.html

@ -34,7 +34,7 @@
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ plugin: pluginSearchText }'>plugin.no-plugins-matching</span>
<span translate translate-values='{ entity: pluginSearchText }'>plugin.no-plugins-matching</span>
</div>
</md-not-found>
</md-autocomplete>

2
ui/src/app/dashboard/states/entity-state-controller.js

@ -109,7 +109,7 @@ export default function EntityStateController($scope, $location, $state, $stateP
if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(
function success(entity) {
var entityName = entityService.entityName(params.entityId.entityType, entity);
var entityName = entity.name;
deferred.resolve(entityName);
},
function fail() {

6
ui/src/app/entity/attribute/attribute-table.tpl.html

@ -128,9 +128,9 @@
<table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
<thead md-head md-order="query.order" md-on-reorder="onReorder">
<tr md-row>
<th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th>
<th md-column md-order-by="key"><span>Key</span></th>
<th md-column>Value</th>
<th md-column md-order-by="lastUpdateTs"><span translate>attribute.last-update-time</span></th>
<th md-column md-order-by="key"><span translate>attribute.key</span></th>
<th md-column><span translate>attribute.value</span></th>
</tr>
</thead>
<tbody md-body>

2
ui/src/app/entity/entity-aliases.controller.js

@ -110,7 +110,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
entityAlias.changed = false;
}
if (!entityAlias.changed && entity && entityAlias.entityType) {
entityAlias.alias = entityService.entityName(entityAlias.entityType, entity);
entityAlias.alias = entity.name;
}
}
}

171
ui/src/app/entity/entity-autocomplete.directive.js

@ -0,0 +1,171 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './entity-autocomplete.scss';
/* eslint-disable import/no-unresolved, import/default */
import entityAutocompleteTemplate from './entity-autocomplete.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityAutocomplete($compile, $templateCache, $q, $filter, entityService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityAutocompleteTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.entity = null;
scope.entitySearchText = '';
scope.fetchEntities = function(searchText) {
var deferred = $q.defer();
entityService.getEntitiesByNameFilter(scope.entityType, searchText, 50, null, scope.entitySubtype).then(function success(result) {
if (result) {
deferred.resolve(result);
} else {
deferred.resolve([]);
}
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
scope.entitySearchTextChanged = function() {
}
scope.updateView = function () {
if (!scope.disabled) {
ngModelCtrl.$setViewValue(scope.entity ? scope.entity.id.id : null);
}
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then(
function success(entity) {
scope.entity = entity;
},
function fail() {
scope.entity = null;
}
);
} else {
scope.entity = null;
}
}
scope.$watch('entityType', function () {
load();
});
scope.$watch('entitySubtype', function () {
if (scope.entity && scope.entity.type != scope.entitySubtype) {
scope.entity = null;
scope.updateView();
}
});
scope.$watch('entity', function () {
scope.updateView();
});
scope.$watch('disabled', function () {
scope.updateView();
});
function load() {
switch (scope.entityType) {
case types.entityType.asset:
scope.selectEntityText = 'asset.select-asset';
scope.entityText = 'asset.asset';
scope.noEntitiesMatchingText = 'asset.no-assets-matching';
scope.entityRequiredText = 'asset.asset-required'
break;
case types.entityType.device:
scope.selectEntityText = 'device.select-device';
scope.entityText = 'device.device';
scope.noEntitiesMatchingText = 'device.no-devices-matching';
scope.entityRequiredText = 'device.device-required'
break;
case types.entityType.rule:
scope.selectEntityText = 'rule.select-rule';
scope.entityText = 'rule.rule';
scope.noEntitiesMatchingText = 'rule.no-rules-matching';
scope.entityRequiredText = 'rule.rule-required'
break;
case types.entityType.plugin:
scope.selectEntityText = 'plugin.select-plugin';
scope.entityText = 'plugin.plugin';
scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
scope.entityRequiredText = 'plugin.plugin-required'
break;
case types.entityType.tenant:
scope.selectEntityText = 'tenant.select-tenant';
scope.entityText = 'tenant.tenant';
scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
scope.entityRequiredText = 'tenant.tenant-required'
break;
case types.entityType.customer:
scope.selectEntityText = 'customer.select-customer';
scope.entityText = 'customer.customer';
scope.noEntitiesMatchingText = 'customer.no-customers-matching';
scope.entityRequiredText = 'customer.customer-required'
break;
case types.entityType.user:
scope.selectEntityText = 'user.select-user';
scope.entityText = 'user.user';
scope.noEntitiesMatchingText = 'user.no-users-matching';
scope.entityRequiredText = 'user.user-required'
break;
case types.entityType.dashboard:
scope.selectEntityText = 'dashboard.select-dashboard';
scope.entityText = 'dashboard.dashboard';
scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
scope.entityRequiredText = 'dashboard.dashboard-required'
break;
case types.entityType.alarm:
scope.selectEntityText = 'alarm.select-alarm';
scope.entityText = 'alarm.alarm';
scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
scope.entityRequiredText = 'alarm.alarm-required'
break;
}
if (scope.entity && scope.entity.id.entityType != scope.entityType) {
scope.entity = null;
scope.updateView();
}
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
entityType: '=',
entitySubtype: '=?'
}
};
}

25
ui/src/app/entity/entity-autocomplete.scss

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-autocomplete {
.tb-entity-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;
}
}

45
ui/src/app/entity/entity-autocomplete.tpl.html

@ -0,0 +1,45 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-autocomplete ng-required="tbRequired"
ng-disabled="disabled"
md-no-cache="true"
md-input-name="entity"
ng-model="entity"
md-selected-item="entity"
md-search-text="entitySearchText"
md-search-text-change="entitySearchTextChanged()"
md-items="item in fetchEntities(entitySearchText)"
md-item-text="item.name"
md-min-length="0"
md-floating-label="{{ entityText | translate }}"
md-select-on-match="true"
md-menu-class="tb-entity-autocomplete">
<md-item-template>
<div class="tb-entity-item">
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
</div>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ entity: entitySearchText }'>{{ noEntitiesMatchingText }}</span>
</div>
</md-not-found>
<div ng-messages="theForm.entity.$error">
<div translate ng-message="required">{{ entityRequiredText }}</div>
</div>
</md-autocomplete>

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

@ -32,14 +32,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
scope.ngModelCtrl = ngModelCtrl;
scope.itemName = function(item) {
if (item) {
return entityService.entityName(scope.entityType, item);
} else {
return '';
}
}
scope.fetchEntities = function(searchText, limit) {
var deferred = $q.defer();
entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {

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

@ -33,7 +33,7 @@
md-min-length="0"
placeholder="{{ 'entity.entity-list' | translate }}">
<md-item-template>
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{itemName(item)}}</span>
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
<span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>

86
ui/src/app/entity/entity-select.directive.js

@ -0,0 +1,86 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './entity-select.scss';
/* eslint-disable import/no-unresolved, import/default */
import entitySelectTemplate from './entity-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntitySelect($compile, $templateCache) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.model = null;
scope.updateView = function () {
if (!scope.disabled) {
var value = ngModelCtrl.$viewValue;
if (scope.model && scope.model.entityType && scope.model.entityId) {
if (!value) {
value = {};
}
value.entityType = scope.model.entityType;
value.id = scope.model.entityId;
ngModelCtrl.$setViewValue(value);
} else {
ngModelCtrl.$setViewValue(null);
}
}
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
scope.model = {};
scope.model.entityType = value.entityType;
scope.model.entityId = value.id;
} else {
scope.model = null;
}
}
scope.$watch('model.entityType', function () {
scope.updateView();
});
scope.$watch('model.entityId', function () {
scope.updateView();
});
scope.$watch('disabled', function () {
scope.updateView();
});
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled'
}
};
}

19
ui/src/app/entity/entity-select.scss

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-select {
}

29
ui/src/app/entity/entity-select.tpl.html

@ -0,0 +1,29 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div layout='row' class="tb-entity-select">
<tb-entity-type-select style="min-width: 100px;"
ng-model="model.entityType">
</tb-entity-type-select>
<tb-entity-autocomplete flex
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
entity-type="model.entityType"
ng-model="model.entityId">
</tb-entity-autocomplete>
</div>

19
ui/src/app/entity/entity-type-select.directive.js

@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntityTypeSelect($compile, $templateCache, userService, types) {
export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityTypeSelectTemplate);
@ -51,10 +51,12 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
scope.entityTypes.customer = types.entityType.customer;
scope.entityTypes.rule = types.entityType.rule;
scope.entityTypes.plugin = types.entityType.plugin;
scope.entityTypes.dashboard = types.entityType.dashboard;
break;
case 'CUSTOMER_USER':
scope.entityTypes.device = types.entityType.device;
scope.entityTypes.asset = types.entityType.asset;
scope.entityTypes.dashboard = types.entityType.dashboard;
break;
}
@ -67,20 +69,7 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
}
scope.typeName = function(type) {
switch (type) {
case types.entityType.device:
return 'entity.type-device';
case types.entityType.asset:
return 'entity.type-asset';
case types.entityType.rule:
return 'entity.type-rule';
case types.entityType.plugin:
return 'entity.type-plugin';
case types.entityType.tenant:
return 'entity.type-tenant';
case types.entityType.customer:
return 'entity.type-customer';
}
return utils.entityTypeName(type);
}
scope.updateValidity = function () {

6
ui/src/app/entity/index.js

@ -18,12 +18,15 @@ import EntityAliasesController from './entity-aliases.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
import EntityAutocompleteDirective from './entity-autocomplete.directive';
import EntitySelectDirective from './entity-select.directive';
import EntityFilterDirective from './entity-filter.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
import AttributeTableDirective from './attribute/attribute-table.directive';
import RelationTableDirective from './relation/relation-table.directive';
export default angular.module('thingsboard.entity', [])
.controller('EntityAliasesController', EntityAliasesController)
@ -33,7 +36,10 @@ export default angular.module('thingsboard.entity', [])
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
.directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
.directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
.directive('tbEntityAutocomplete', EntityAutocompleteDirective)
.directive('tbEntitySelect', EntitySelectDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
.directive('tbRelationTable', RelationTableDirective)
.name;

43
ui/src/app/entity/relation/add-relation-dialog.controller.js

@ -0,0 +1,43 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*@ngInject*/
export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, from) {
var vm = this;
vm.types = types;
vm.relation = {};
vm.relation.from = from;
vm.relation.type = types.entityRelationType.contains;
vm.add = add;
vm.cancel = cancel;
function cancel() {
$mdDialog.cancel();
}
function add() {
$scope.theForm.$setPristine();
entityRelationService.saveRelation(vm.relation).then(
function success() {
$mdDialog.hide();
}
);
}
}

64
ui/src/app/entity/relation/add-relation-dialog.tpl.html

@ -0,0 +1,64 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'relation.add' | translate }}" style="min-width: 400px;">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>relation.add</h2>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading">
<md-input-container class="md-block">
<label translate>relation.relation-type</label>
<md-select required ng-model="vm.relation.type" ng-disabled="loading">
<md-option ng-repeat="type in vm.types.entityRelationType" ng-value="type">
<span>{{('relation.relation-types.' + type) | translate}}</span>
</md-option>
</md-select>
</md-input-container>
<span class="tb-small">{{'entity.entity' | translate }}</span>
<tb-entity-select flex
the-form="theForm"
tb-required="true"
ng-model="vm.relation.to">
</tb-entity-select>
</fieldset>
</md-content>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
class="md-raised md-primary">
{{ 'action.add' | translate }}
</md-button>
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
translate }}
</md-button>
</md-dialog-actions>
</form>
</md-dialog>

179
ui/src/app/entity/relation/relation-table.directive.js

@ -0,0 +1,179 @@
/*
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'angular-material-data-table/dist/md-data-table.min.css';
import './relation-table.scss';
/* eslint-disable import/no-unresolved, import/default */
import relationTableTemplate from './relation-table.tpl.html';
import addRelationTemplate from './add-relation-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import AddRelationController from './add-relation-dialog.controller';
/*@ngInject*/
export default function RelationTable() {
return {
restrict: "E",
scope: true,
bindToController: {
entityId: '=',
entityType: '@'
},
controller: RelationTableController,
controllerAs: 'vm',
templateUrl: relationTableTemplate
};
}
/*@ngInject*/
function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, utils, types, entityRelationService) {
let vm = this;
vm.relations = [];
vm.relationsCount = 0;
vm.allRelations = [];
vm.selectedRelations = [];
vm.query = {
order: 'typeName',
limit: 5,
page: 1,
search: null
};
vm.enterFilterMode = enterFilterMode;
vm.exitFilterMode = exitFilterMode;
vm.onReorder = onReorder;
vm.onPaginate = onPaginate;
vm.addRelation = addRelation;
vm.editRelation = editRelation;
vm.deleteRelation = deleteRelation;
vm.deleteRelations = deleteRelations;
vm.reloadRelations = reloadRelations;
vm.updateRelations = updateRelations;
$scope.$watch("vm.entityId", function(newVal, prevVal) {
if (newVal && !angular.equals(newVal, prevVal)) {
reloadRelations();
}
});
$scope.$watch("vm.query.search", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
updateRelations();
}
});
function enterFilterMode () {
vm.query.search = '';
}
function exitFilterMode () {
vm.query.search = null;
updateRelations();
}
function onReorder () {
updateRelations();
}
function onPaginate () {
updateRelations();
}
function addRelation($event) {
if ($event) {
$event.stopPropagation();
}
var from = {
id: vm.entityId,
entityType: vm.entityType
};
$mdDialog.show({
controller: AddRelationController,
controllerAs: 'vm',
templateUrl: addRelationTemplate,
parent: angular.element($document[0].body),
locals: { from: from },
fullscreen: true,
targetEvent: $event
}).then(function () {
reloadRelations();
}, function () {
});
}
function editRelation($event, /*relation*/) {
if ($event) {
$event.stopPropagation();
}
//TODO:
}
function deleteRelation($event, /*relation*/) {
if ($event) {
$event.stopPropagation();
}
//TODO:
}
function deleteRelations($event) {
if ($event) {
$event.stopPropagation();
}
//TODO:
}
function reloadRelations () {
vm.allRelations.length = 0;
vm.relations.length = 0;
vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
vm.relationsPromise.then(
function success(allRelations) {
allRelations.forEach(function(relation) {
relation.typeName = $translate.instant('relation.relation-type.' + relation.type);
relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
});
vm.allRelations = allRelations;
vm.selectedRelations = [];
vm.updateRelations();
vm.relationsPromise = null;
},
function fail() {
vm.allRelations = [];
vm.selectedRelations = [];
vm.updateRelations();
vm.relationsPromise = null;
}
)
}
function updateRelations () {
vm.selectedRelations = [];
var result = $filter('orderBy')(vm.allRelations, vm.query.order);
if (vm.query.search != null) {
result = $filter('filter')(result, {$: vm.query.search});
}
vm.relationsCount = result.length;
var startIndex = vm.query.limit * (vm.query.page - 1);
vm.relations = result.slice(startIndex, startIndex + vm.query.limit);
}
}

28
ui/src/app/entity/relation/relation-table.scss

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../scss/constants';
$md-light: rgba(255, 255, 255, 100%);
.tb-relation-table {
md-toolbar.md-table-toolbar.alternate {
.md-toolbar-tools {
md-icon {
color: $md-light;
}
}
}
}

119
ui/src/app/entity/relation/relation-table.tpl.html

@ -0,0 +1,119 @@
<!--
Copyright © 2016-2017 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-content flex class="md-padding tb-absolute-fill tb-relation-table tb-data-table" layout="column">
<div layout="column" class="md-whiteframe-z1">
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
&& vm.query.search === null">
<div class="md-toolbar-tools">
<span translate>relation.entity-relations</span>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.addRelation($event)">
<md-icon>add</md-icon>
<md-tooltip md-direction="top">
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="vm.reloadRelations()">
<md-icon>refresh</md-icon>
<md-tooltip md-direction="top">
{{ 'action.refresh' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
&& vm.query.search != null">
<div class="md-toolbar-tools">
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
<md-tooltip md-direction="top">
{{ 'action.close' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedRelations.length">
<div class="md-toolbar-tools">
<span translate
translate-values="{count: selectedRelations.length}"
translate-interpolation="messageformat">relation.selected-relations</span>
<span flex></span>
<md-button class="md-icon-button" ng-click="vm.deleteRelations($event)">
<md-icon>delete</md-icon>
<md-tooltip md-direction="top">
{{ 'action.delete' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<md-table-container>
<table md-table md-row-select multiple="" ng-model="vm.selectedRelations" md-progress="vm.relationsDeferred.promise">
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
<th md-column md-order-by="typeName"><span translate>relation.type</span></th>
<th md-column md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
<th md-column md-order-by="toName"><span translate>relation.to-entity-name</span></th>
<th md-column><span>&nbsp</span></th>
</tr>
</thead>
<tbody md-body>
<tr md-row md-select="relation" md-select-id="relation" md-auto-select ng-repeat="relation in vm.relations">
<td md-cell>{{ relation.typeName }}</td>
<td md-cell>{{ relation.toEntityTypeName }}</td>
<td md-cell>{{ relation.toName }}</td>
<td md-cell class="tb-action-cell">
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
ng-click="vm.editRelation($event, relation)">
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
<md-tooltip md-direction="top">
{{ 'relation.edit' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteRelation($event, relation)">
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
<md-tooltip md-direction="top">
{{ 'relation.delete' | translate }}
</md-tooltip>
</md-button>
</td>
</tr>
</tbody>
</table>
</md-table-container>
<md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
md-page="vm.query.page" md-total="{{vm.relationsCount}}"
md-on-paginate="onPaginate" md-page-select>
</md-table-pagination>
</div>
</md-content>

6
ui/src/app/locale/locale.constant-es.js

@ -235,7 +235,7 @@
"socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
"select-dashboard": "Seleccionar panel",
"no-dashboards-matching": "Panel '{{dashboard}}' no encontrado.",
"no-dashboards-matching": "Panel '{{entity}}' no encontrado.",
"dashboard-required": "Panel requerido.",
"select-existing": "Seleccionar paneles existentes",
"create-new": "Crear nuevo panel",
@ -330,7 +330,7 @@
"create-new-key": "Crear nueva clave!",
"duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",
"configure-alias": "Configurar alias '{{alias}}'",
"no-devices-matching": "No se encontró dispositivo '{{device}}'",
"no-devices-matching": "No se encontró dispositivo '{{entity}}'",
"alias": "Alias",
"alias-required": "Alias de dispositivo requerido.",
"remove-alias": "Eliminar alias",
@ -529,7 +529,7 @@
"system": "Sistema",
"select-plugin": "plugin",
"plugin": "Plugin",
"no-plugins-matching": "No se encontraron plugins: '{{plugin}}'",
"no-plugins-matching": "No se encontraron plugins: '{{entity}}'",
"plugin-required": "Plugin requerido.",
"plugin-require-match": "Por favor, elija un plugin existente.",
"events": "Eventos",

6
ui/src/app/locale/locale.constant-ko.js

@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) {
"unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?",
"unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.",
"select-dashboard": "대시보드 선택",
"no-dashboards-matching": "'{{dashboard}}'와 일치하는 대시보드가 없습니다.",
"no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.",
"dashboard-required": "대시보드를 입력하세요.",
"select-existing": "기존 대시보드 선택",
"create-new": "대시보드 생성",
@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) {
"create-new-key": "새로 만들기!",
"duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.<br> 디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.",
"configure-alias": "'{{alias}}' 앨리어스 구성",
"no-devices-matching": "'{{device}}'와 일치하는 디바이스를 찾을 수 없습니다.",
"no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.",
"alias": "앨리어스",
"alias-required": "디바이스 앨리어스를 입력하세요.",
"remove-alias": "디바이스 앨리어스 삭제",
@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) {
"system": "시스템",
"select-plugin": "플러그인 선택",
"plugin": "플러그인",
"no-plugins-matching": "'{{plugin}}'과 일치하는 플러그인을 찾을 수 없습니다.",
"no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.",
"plugin-required": "플러그인을 입력하세요.",
"plugin-require-match": "기존의 플러그인을 선택해주세요.",
"events": "이벤트",

6
ui/src/app/locale/locale.constant-ru.js

@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) {
"socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard",
"select-dashboard": "Выберите дашборд",
"no-dashboards-matching": "Дашборд '{{dashboard}}' не найден.",
"no-dashboards-matching": "Дашборд '{{entity}}' не найден.",
"dashboard-required": "Дашборд обязателен.",
"select-existing": "Выберите существующий дашборд",
"create-new": "Создать новый дашборд",
@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) {
"create-new-key": "Создать новый!",
"duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.",
"configure-alias": "Конфигурировать '{{alias}}' псевдоним",
"no-devices-matching": "Устройство '{{device}}' не найдено.",
"no-devices-matching": "Устройство '{{entity}}' не найдено.",
"alias": "Псевдоним",
"alias-required": "Псевдоним устройства обязателен.",
"remove-alias": "Удалить псевдоним устройства",
@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) {
"system": "Системный",
"select-plugin": "Выберите плагин",
"plugin": "Плагин",
"no-plugins-matching": "Плагин '{{plugin}}' не найден.",
"no-plugins-matching": "Плагин '{{entity}}' не найден.",
"plugin-required": "Плагин обязателен.",
"plugin-require-match": "Пожалуйста, выберите существующий плагин.",
"events": "События",

6
ui/src/app/locale/locale.constant-zh.js

@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) {
"socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
"socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
"select-dashboard" : "选择仪表板",
"no-dashboards-matching" : "找不到符合 '{{dashboard}}' 的仪表板。",
"no-dashboards-matching" : "找不到符合 '{{entity}}' 的仪表板。",
"dashboard-required" : "仪表板是必需的。",
"select-existing" : "选择现有仪表板",
"create-new" : "创建新的仪表板",
@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) {
"create-new-key": "创建一个新的!",
"duplicate-alias-error" : "找到重复别名 '{{alias}}'。 <br> 设备别名必须是唯一的。",
"configure-alias" : "配置 '{{alias}}' 别名",
"no-devices-matching" : "找不到与 '{{device}}' 匹配的设备。",
"no-devices-matching" : "找不到与 '{{entity}}' 匹配的设备。",
"alias" : "别名",
"alias-required" : "需要设备别名。",
"remove-alias": "删除设备别名",
@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) {
"system" : "系统",
"select-plugin" : "选择插件",
"plugin" : "插件",
"no-plugins-matching" : "没有找到匹配'{{plugin}}'的插件。",
"no-plugins-matching" : "没有找到匹配'{{entity}}'的插件。",
"plugin-required" : "插件是必需的。",
"plugin-require-match" : "请选择一个现有的插件。",
"events" : "事件",

65
ui/src/app/locale/locale.constant.js

@ -106,6 +106,12 @@ export default angular.module('thingsboard.locale', [])
"enable-tls": "Enable TLS",
"send-test-mail": "Send test mail"
},
"alarm": {
"alarm": "Alarm",
"select-alarm": "Select alarm",
"no-alarms-matching": "No alarms matching '{{entity}}' were found.",
"alarm-required": "Alarm is required"
},
"asset": {
"asset": "Asset",
"assets": "Assets",
@ -157,7 +163,10 @@ export default angular.module('thingsboard.locale', [])
"unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
"unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
"copyId": "Copy asset Id",
"idCopiedMessage": "Asset Id has been copied to clipboard"
"idCopiedMessage": "Asset Id has been copied to clipboard",
"select-asset": "Select asset",
"no-assets-matching": "No assets matching '{{entity}}' were found.",
"asset-required": "Asset is required"
},
"attribute": {
"attributes": "Attributes",
@ -169,6 +178,7 @@ export default angular.module('thingsboard.locale', [])
"scope-shared": "Shared attributes",
"add": "Add attribute",
"key": "Key",
"last-update-time": "Last update time",
"key-required": "Attribute key is required.",
"value": "Value",
"value-required": "Attribute value is required.",
@ -210,6 +220,7 @@ export default angular.module('thingsboard.locale', [])
"enter-search": "Enter search"
},
"customer": {
"customer": "Customer",
"customers": "Customers",
"management": "Customer management",
"dashboard": "Customer Dashboard",
@ -246,7 +257,10 @@ export default angular.module('thingsboard.locale', [])
"details": "Details",
"events": "Events",
"copyId": "Copy customer Id",
"idCopiedMessage": "Customer Id has been copied to clipboard"
"idCopiedMessage": "Customer Id has been copied to clipboard",
"select-customer": "Select customer",
"no-customers-matching": "No customers matching '{{entity}}' were found.",
"customer-required": "Customer is required"
},
"datetime": {
"date-from": "Date from",
@ -304,7 +318,7 @@ export default angular.module('thingsboard.locale', [])
"socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
"select-dashboard": "Select dashboard",
"no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
"no-dashboards-matching": "No dashboards matching '{{entity}}' were found.",
"dashboard-required": "Dashboard is required.",
"select-existing": "Select existing dashboard",
"create-new": "Create new dashboard",
@ -425,7 +439,7 @@ export default angular.module('thingsboard.locale', [])
"create-new-key": "Create a new one!",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
"configure-alias": "Configure '{{alias}}' alias",
"no-devices-matching": "No devices matching '{{device}}' were found.",
"no-devices-matching": "No devices matching '{{entity}}' were found.",
"alias": "Alias",
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
@ -497,7 +511,8 @@ export default angular.module('thingsboard.locale', [])
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"is-gateway": "Is gateway",
"public": "Public",
"device-public": "Device is public"
"device-public": "Device is public",
"select-device": "Select device"
},
"dialog": {
"close": "Close dialog"
@ -535,6 +550,9 @@ export default angular.module('thingsboard.locale', [])
"type-plugin": "Plugin",
"type-tenant": "Tenant",
"type-customer": "Customer",
"type-user": "User",
"type-dashboard": "Dashboard",
"type-alarm": "Alarm",
"select-entities": "Select entities",
"no-aliases-found": "No aliases found.",
"no-alias-matching": "'{{alias}}' not found.",
@ -672,7 +690,7 @@ export default angular.module('thingsboard.locale', [])
"system": "System",
"select-plugin": "Select plugin",
"plugin": "Plugin",
"no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
"no-plugins-matching": "No plugins matching '{{entity}}' were found.",
"plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.",
"events": "Events",
@ -685,6 +703,7 @@ export default angular.module('thingsboard.locale', [])
"invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
"copyId": "Copy plugin Id",
"idCopiedMessage": "Plugin Id has been copied to clipboard"
},
"position": {
"top": "Top",
@ -697,7 +716,24 @@ export default angular.module('thingsboard.locale', [])
"change-password": "Change Password",
"current-password": "Current password"
},
"relation": {
"relations": "Relations",
"entity-relations": "Entity relations",
"selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
"type": "Type",
"to-entity-type": "Entity type",
"to-entity-name": "Entity name",
"edit": "Edit relation",
"delete": "Delete relation",
"relation-type": "Relation type",
"relation-types": {
"Contains": "Contains",
"Manages": "Manages"
},
"add": "Add relation"
},
"rule": {
"rule": "Rule",
"rules": "Rules",
"delete": "Delete rule",
"activate": "Activate rule",
@ -749,12 +785,16 @@ export default angular.module('thingsboard.locale', [])
"rule-file": "Rule file",
"invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.",
"copyId": "Copy rule Id",
"idCopiedMessage": "Rule Id has been copied to clipboard"
"idCopiedMessage": "Rule Id has been copied to clipboard",
"select-rule": "Select rule",
"no-rules-matching": "No rules matching '{{entity}}' were found.",
"rule-required": "Rule is required"
},
"rule-plugin": {
"management": "Rules and plugins management"
},
"tenant": {
"tenant": "Tenant",
"tenants": "Tenants",
"management": "Tenant management",
"add": "Add Tenant",
@ -775,7 +815,10 @@ export default angular.module('thingsboard.locale', [])
"details": "Details",
"events": "Events",
"copyId": "Copy tenant Id",
"idCopiedMessage": "Tenant Id has been copied to clipboard"
"idCopiedMessage": "Tenant Id has been copied to clipboard",
"select-tenant": "Select tenant",
"no-tenants-matching": "No tenants matching '{{entity}}' were found.",
"tenant-required": "Tenant is required"
},
"timeinterval": {
"seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
@ -803,6 +846,7 @@ export default angular.module('thingsboard.locale', [])
"time-period": "Time period"
},
"user": {
"user": "User",
"users": "Users",
"customer-users": "Customer Users",
"tenant-admins": "Tenant Admins",
@ -828,7 +872,10 @@ export default angular.module('thingsboard.locale', [])
"last-name": "Last Name",
"description": "Description",
"default-dashboard": "Default dashboard",
"always-fullscreen": "Always fullscreen"
"always-fullscreen": "Always fullscreen",
"select-user": "Select user",
"no-users-matching": "No users matching '{{entity}}' were found.",
"user-required": "User is required"
},
"value": {
"type": "Value type",

39
ui/src/scss/main.scss

@ -261,6 +261,45 @@ pre.tb-highlight {
font-size: 16px;
}
.tb-data-table {
md-toolbar {
z-index: 0;
}
span.no-data-found {
position: relative;
height: calc(100% - 57px);
text-transform: uppercase;
display: flex;
}
table.md-table {
tbody {
tr {
td {
&.tb-action-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 72px;
max-width: 72px;
width: 72px;
.md-button {
&.md-icon-button {
margin: 0;
padding: 6px;
width: 36px;
height: 36px;
}
}
.tb-spacer {
padding-left: 38px;
}
}
}
}
}
}
}
/***********************
* Flow

Loading…
Cancel
Save