Commit 9f3de065478e168189a2857b28b5b40c8ce0491d

Authored by Andrew Shvayka
2 parents d6ad8c9e e3976b31

Merge with master

Showing 89 changed files with 2288 additions and 307 deletions
@@ -343,7 +343,7 @@ public abstract class BaseController { @@ -343,7 +343,7 @@ public abstract class BaseController {
343 Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException { 343 Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException {
344 try { 344 try {
345 validateId(alarmId, "Incorrect alarmId " + alarmId); 345 validateId(alarmId, "Incorrect alarmId " + alarmId);
346 - Alarm alarm = alarmService.findAlarmById(alarmId).get(); 346 + Alarm alarm = alarmService.findAlarmByIdAsync(alarmId).get();
347 checkAlarm(alarm); 347 checkAlarm(alarm);
348 return alarm; 348 return alarm;
349 } catch (Exception e) { 349 } catch (Exception e) {
@@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*; @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
21 import org.thingsboard.server.common.data.id.EntityId; 21 import org.thingsboard.server.common.data.id.EntityId;
22 import org.thingsboard.server.common.data.id.EntityIdFactory; 22 import org.thingsboard.server.common.data.id.EntityIdFactory;
23 import org.thingsboard.server.common.data.relation.EntityRelation; 23 import org.thingsboard.server.common.data.relation.EntityRelation;
  24 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
24 import org.thingsboard.server.dao.relation.EntityRelationsQuery; 25 import org.thingsboard.server.dao.relation.EntityRelationsQuery;
25 import org.thingsboard.server.exception.ThingsboardErrorCode; 26 import org.thingsboard.server.exception.ThingsboardErrorCode;
26 import org.thingsboard.server.exception.ThingsboardException; 27 import org.thingsboard.server.exception.ThingsboardException;
@@ -128,6 +129,21 @@ public class EntityRelationController extends BaseController { @@ -128,6 +129,21 @@ public class EntityRelationController extends BaseController {
128 } 129 }
129 130
130 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 131 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  132 + @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {"fromId", "fromType"})
  133 + @ResponseBody
  134 + public List<EntityRelationInfo> findInfoByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType) throws ThingsboardException {
  135 + checkParameter("fromId", strFromId);
  136 + checkParameter("fromType", strFromType);
  137 + EntityId entityId = EntityIdFactory.getByTypeAndId(strFromType, strFromId);
  138 + checkEntityId(entityId);
  139 + try {
  140 + return checkNotNull(relationService.findInfoByFrom(entityId).get());
  141 + } catch (Exception e) {
  142 + throw handleException(e);
  143 + }
  144 + }
  145 +
  146 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
131 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"}) 147 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
132 @ResponseBody 148 @ResponseBody
133 public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType 149 public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType
@@ -19,12 +19,18 @@ server: @@ -19,12 +19,18 @@ server:
19 address: "${HTTP_BIND_ADDRESS:0.0.0.0}" 19 address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
20 # Server bind port 20 # Server bind port
21 port: "${HTTP_BIND_PORT:8080}" 21 port: "${HTTP_BIND_PORT:8080}"
22 -# Uncomment the following section to enable ssl  
23 -# ssl:  
24 -# key-store: classpath:keystore/keystore.p12  
25 -# key-store-password: thingsboard  
26 -# keyStoreType: PKCS12  
27 -# keyAlias: tomcat 22 + # Server SSL configuration
  23 + ssl:
  24 + # Enable/disable SSL support
  25 + enabled: "${SSL_ENABLED:false}"
  26 + # Path to the key store that holds the SSL certificate
  27 + key-store: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
  28 + # Password used to access the key store
  29 + key-store-password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
  30 + # Type of the key store
  31 + key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}"
  32 + # Alias that identifies the key in the key store
  33 + key-alias: "${SSL_KEY_ALIAS:tomcat}"
28 34
29 # Zookeeper connection parameters. Used for service discovery. 35 # Zookeeper connection parameters. Used for service discovery.
30 zk: 36 zk:
@@ -79,12 +85,18 @@ mqtt: @@ -79,12 +85,18 @@ mqtt:
79 leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}" 85 leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}"
80 boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" 86 boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}"
81 worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" 87 worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}"
82 -# Uncomment the following lines to enable ssl for MQTT  
83 -# ssl:  
84 -# key_store: mqttserver.jks  
85 -# key_store_password: server_ks_password  
86 -# key_password: server_key_password  
87 -# key_store_type: JKS 88 + # MQTT SSL configuration
  89 + ssl:
  90 + # Enable/disable SSL support
  91 + enabled: "${MQTT_SSL_ENABLED:false}"
  92 + # Path to the key store that holds the SSL certificate
  93 + key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}"
  94 + # Password used to access the key store
  95 + key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}"
  96 + # Password used to access the key
  97 + key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}"
  98 + # Type of the key store
  99 + key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
88 100
89 # CoAP server parameters 101 # CoAP server parameters
90 coap: 102 coap:
@@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20
21 import com.fasterxml.jackson.databind.JsonNode; 21 import com.fasterxml.jackson.databind.JsonNode;
22 22
23 -public class Customer extends ContactBased<CustomerId>{ 23 +public class Customer extends ContactBased<CustomerId> implements HasName {
24 24
25 private static final long serialVersionUID = -1599722990298929275L; 25 private static final long serialVersionUID = -1599722990298929275L;
26 26
@@ -59,6 +59,11 @@ public class Customer extends ContactBased<CustomerId>{ @@ -59,6 +59,11 @@ public class Customer extends ContactBased<CustomerId>{
59 this.title = title; 59 this.title = title;
60 } 60 }
61 61
  62 + @Override
  63 + public String getName() {
  64 + return title;
  65 + }
  66 +
62 public JsonNode getAdditionalInfo() { 67 public JsonNode getAdditionalInfo() {
63 return additionalInfo; 68 return additionalInfo;
64 } 69 }
@@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
19 import org.thingsboard.server.common.data.id.DashboardId; 19 import org.thingsboard.server.common.data.id.DashboardId;
20 import org.thingsboard.server.common.data.id.TenantId; 20 import org.thingsboard.server.common.data.id.TenantId;
21 21
22 -public class DashboardInfo extends SearchTextBased<DashboardId> { 22 +public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
23 23
24 private TenantId tenantId; 24 private TenantId tenantId;
25 private CustomerId customerId; 25 private CustomerId customerId;
@@ -65,6 +65,11 @@ public class DashboardInfo extends SearchTextBased<DashboardId> { @@ -65,6 +65,11 @@ public class DashboardInfo extends SearchTextBased<DashboardId> {
65 } 65 }
66 66
67 @Override 67 @Override
  68 + public String getName() {
  69 + return title;
  70 + }
  71 +
  72 + @Override
68 public String getSearchText() { 73 public String getSearchText() {
69 return title; 74 return title;
70 } 75 }
@@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId;
21 21
22 import com.fasterxml.jackson.databind.JsonNode; 22 import com.fasterxml.jackson.databind.JsonNode;
23 23
24 -public class Device extends SearchTextBased<DeviceId> { 24 +public class Device extends SearchTextBased<DeviceId> implements HasName {
25 25
26 private static final long serialVersionUID = 2807343040519543363L; 26 private static final long serialVersionUID = 2807343040519543363L;
27 27
@@ -64,6 +64,7 @@ public class Device extends SearchTextBased<DeviceId> { @@ -64,6 +64,7 @@ public class Device extends SearchTextBased<DeviceId> {
64 this.customerId = customerId; 64 this.customerId = customerId;
65 } 65 }
66 66
  67 + @Override
67 public String getName() { 68 public String getName() {
68 return name; 69 return name;
69 } 70 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data;
  17 +
  18 +public interface HasName {
  19 +
  20 + String getName();
  21 +
  22 +}
@@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.TenantId;
19 19
20 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
21 21
22 -public class Tenant extends ContactBased<TenantId>{ 22 +public class Tenant extends ContactBased<TenantId> implements HasName {
23 23
24 private static final long serialVersionUID = 8057243243859922101L; 24 private static final long serialVersionUID = 8057243243859922101L;
25 25
@@ -50,6 +50,11 @@ public class Tenant extends ContactBased<TenantId>{ @@ -50,6 +50,11 @@ public class Tenant extends ContactBased<TenantId>{
50 this.title = title; 50 this.title = title;
51 } 51 }
52 52
  53 + @Override
  54 + public String getName() {
  55 + return title;
  56 + }
  57 +
53 public String getRegion() { 58 public String getRegion() {
54 return region; 59 return region;
55 } 60 }
@@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.security.Authority; @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.security.Authority;
22 22
23 import com.fasterxml.jackson.databind.JsonNode; 23 import com.fasterxml.jackson.databind.JsonNode;
24 24
25 -public class User extends SearchTextBased<UserId> { 25 +public class User extends SearchTextBased<UserId> implements HasName {
26 26
27 private static final long serialVersionUID = 8250339805336035966L; 27 private static final long serialVersionUID = 8250339805336035966L;
28 28
@@ -77,6 +77,11 @@ public class User extends SearchTextBased<UserId> { @@ -77,6 +77,11 @@ public class User extends SearchTextBased<UserId> {
77 this.email = email; 77 this.email = email;
78 } 78 }
79 79
  80 + @Override
  81 + public String getName() {
  82 + return email;
  83 + }
  84 +
80 public Authority getAuthority() { 85 public Authority getAuthority() {
81 return authority; 86 return authority;
82 } 87 }
@@ -21,6 +21,7 @@ import lombok.Builder; @@ -21,6 +21,7 @@ import lombok.Builder;
21 import lombok.Data; 21 import lombok.Data;
22 import org.thingsboard.server.common.data.BaseData; 22 import org.thingsboard.server.common.data.BaseData;
23 import org.thingsboard.server.common.data.id.AssetId; 23 import org.thingsboard.server.common.data.id.AssetId;
  24 +import org.thingsboard.server.common.data.HasName;
24 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
26 27
@@ -30,7 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -30,7 +31,7 @@ import org.thingsboard.server.common.data.id.TenantId;
30 @Data 31 @Data
31 @Builder 32 @Builder
32 @AllArgsConstructor 33 @AllArgsConstructor
33 -public class Alarm extends BaseData<AlarmId> { 34 +public class Alarm extends BaseData<AlarmId> implements HasName {
34 35
35 private TenantId tenantId; 36 private TenantId tenantId;
36 private String type; 37 private String type;
@@ -52,4 +53,8 @@ public class Alarm extends BaseData<AlarmId> { @@ -52,4 +53,8 @@ public class Alarm extends BaseData<AlarmId> {
52 super(id); 53 super(id);
53 } 54 }
54 55
  56 + @Override
  57 + public String getName() {
  58 + return type;
  59 + }
55 } 60 }
@@ -16,12 +16,13 @@ @@ -16,12 +16,13 @@
16 package org.thingsboard.server.common.data.asset; 16 package org.thingsboard.server.common.data.asset;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import org.thingsboard.server.common.data.HasName;
19 import org.thingsboard.server.common.data.SearchTextBased; 20 import org.thingsboard.server.common.data.SearchTextBased;
20 import org.thingsboard.server.common.data.id.AssetId; 21 import org.thingsboard.server.common.data.id.AssetId;
21 import org.thingsboard.server.common.data.id.CustomerId; 22 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
23 24
24 -public class Asset extends SearchTextBased<AssetId> { 25 +public class Asset extends SearchTextBased<AssetId> implements HasName {
25 26
26 private static final long serialVersionUID = 2807343040519543363L; 27 private static final long serialVersionUID = 2807343040519543363L;
27 28
@@ -64,6 +65,7 @@ public class Asset extends SearchTextBased<AssetId> { @@ -64,6 +65,7 @@ public class Asset extends SearchTextBased<AssetId> {
64 this.customerId = customerId; 65 this.customerId = customerId;
65 } 66 }
66 67
  68 + @Override
67 public String getName() { 69 public String getName() {
68 return name; 70 return name;
69 } 71 }
@@ -15,13 +15,14 @@ @@ -15,13 +15,14 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.plugin; 16 package org.thingsboard.server.common.data.plugin;
17 17
  18 +import org.thingsboard.server.common.data.HasName;
18 import org.thingsboard.server.common.data.SearchTextBased; 19 import org.thingsboard.server.common.data.SearchTextBased;
19 import org.thingsboard.server.common.data.id.PluginId; 20 import org.thingsboard.server.common.data.id.PluginId;
20 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
21 22
22 import com.fasterxml.jackson.databind.JsonNode; 23 import com.fasterxml.jackson.databind.JsonNode;
23 24
24 -public class PluginMetaData extends SearchTextBased<PluginId> { 25 +public class PluginMetaData extends SearchTextBased<PluginId> implements HasName {
25 26
26 private static final long serialVersionUID = 1L; 27 private static final long serialVersionUID = 1L;
27 28
@@ -75,6 +76,7 @@ public class PluginMetaData extends SearchTextBased<PluginId> { @@ -75,6 +76,7 @@ public class PluginMetaData extends SearchTextBased<PluginId> {
75 this.tenantId = tenantId; 76 this.tenantId = tenantId;
76 } 77 }
77 78
  79 + @Override
78 public String getName() { 80 public String getName() {
79 return name; 81 return name;
80 } 82 }
@@ -47,11 +47,11 @@ public class EntityRelation { @@ -47,11 +47,11 @@ public class EntityRelation {
47 this.additionalInfo = additionalInfo; 47 this.additionalInfo = additionalInfo;
48 } 48 }
49 49
50 - public EntityRelation(EntityRelation device) {  
51 - this.from = device.getFrom();  
52 - this.to = device.getTo();  
53 - this.type = device.getType();  
54 - this.additionalInfo = device.getAdditionalInfo(); 50 + public EntityRelation(EntityRelation entityRelation) {
  51 + this.from = entityRelation.getFrom();
  52 + this.to = entityRelation.getTo();
  53 + this.type = entityRelation.getType();
  54 + this.additionalInfo = entityRelation.getAdditionalInfo();
55 } 55 }
56 56
57 public EntityId getFrom() { 57 public EntityId getFrom() {
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.common.data.relation;
  18 +
  19 +public class EntityRelationInfo extends EntityRelation {
  20 +
  21 + private static final long serialVersionUID = 2807343097519543363L;
  22 +
  23 + private String toName;
  24 +
  25 + public EntityRelationInfo() {
  26 + super();
  27 + }
  28 +
  29 + public EntityRelationInfo(EntityRelation entityRelation) {
  30 + super(entityRelation);
  31 + }
  32 +
  33 + public String getToName() {
  34 + return toName;
  35 + }
  36 +
  37 + public void setToName(String toName) {
  38 + this.toName = toName;
  39 + }
  40 +
  41 + @Override
  42 + public boolean equals(Object o) {
  43 + if (this == o) return true;
  44 + if (o == null || getClass() != o.getClass()) return false;
  45 + if (!super.equals(o)) return false;
  46 +
  47 + EntityRelationInfo that = (EntityRelationInfo) o;
  48 +
  49 + return toName != null ? toName.equals(that.toName) : that.toName == null;
  50 +
  51 + }
  52 +
  53 + @Override
  54 + public int hashCode() {
  55 + int result = super.hashCode();
  56 + result = 31 * result + (toName != null ? toName.hashCode() : 0);
  57 + return result;
  58 + }
  59 +}
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule; @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
17 17
18 import lombok.Data; 18 import lombok.Data;
19 import lombok.ToString; 19 import lombok.ToString;
  20 +import org.thingsboard.server.common.data.HasName;
20 import org.thingsboard.server.common.data.SearchTextBased; 21 import org.thingsboard.server.common.data.SearchTextBased;
21 import org.thingsboard.server.common.data.id.CustomerId; 22 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.RuleId; 23 import org.thingsboard.server.common.data.id.RuleId;
@@ -26,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode; @@ -26,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
26 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; 27 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
27 28
28 @Data 29 @Data
29 -public class RuleMetaData extends SearchTextBased<RuleId> { 30 +public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
30 31
31 private static final long serialVersionUID = -5656679015122935465L; 32 private static final long serialVersionUID = -5656679015122935465L;
32 33
@@ -66,4 +67,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> { @@ -66,4 +67,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> {
66 return name; 67 return name;
67 } 68 }
68 69
  70 + @Override
  71 + public String getName() {
  72 + return name;
  73 + }
  74 +
69 } 75 }
@@ -32,7 +32,7 @@ public interface AlarmService { @@ -32,7 +32,7 @@ public interface AlarmService {
32 32
33 ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs); 33 ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long ackTs);
34 34
35 - ListenableFuture<Alarm> findAlarmById(AlarmId alarmId); 35 + ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
36 36
37 ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query); 37 ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
38 38
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
30 import org.thingsboard.server.common.data.id.EntityId; 30 import org.thingsboard.server.common.data.id.EntityId;
31 import org.thingsboard.server.common.data.page.TimePageData; 31 import org.thingsboard.server.common.data.page.TimePageData;
32 import org.thingsboard.server.common.data.relation.EntityRelation; 32 import org.thingsboard.server.common.data.relation.EntityRelation;
  33 +import org.thingsboard.server.dao.entity.AbstractEntityService;
33 import org.thingsboard.server.dao.entity.BaseEntityService; 34 import org.thingsboard.server.dao.entity.BaseEntityService;
34 import org.thingsboard.server.dao.exception.DataValidationException; 35 import org.thingsboard.server.dao.exception.DataValidationException;
35 import org.thingsboard.server.dao.model.*; 36 import org.thingsboard.server.dao.model.*;
@@ -54,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*; @@ -54,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
54 55
55 @Service 56 @Service
56 @Slf4j 57 @Slf4j
57 -public class BaseAlarmService extends BaseEntityService implements AlarmService { 58 +public class BaseAlarmService extends AbstractEntityService implements AlarmService {
58 59
59 public static final String ALARM_RELATION_PREFIX = "ALARM_"; 60 public static final String ALARM_RELATION_PREFIX = "ALARM_";
60 public static final String ALARM_RELATION = "ALARM_ANY"; 61 public static final String ALARM_RELATION = "ALARM_ANY";
@@ -190,7 +191,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService @@ -190,7 +191,7 @@ public class BaseAlarmService extends BaseEntityService implements AlarmService
190 } 191 }
191 192
192 @Override 193 @Override
193 - public ListenableFuture<Alarm> findAlarmById(AlarmId alarmId) { 194 + public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) {
194 log.trace("Executing findAlarmById [{}]", alarmId); 195 log.trace("Executing findAlarmById [{}]", alarmId);
195 validateId(alarmId, "Incorrect alarmId " + alarmId); 196 validateId(alarmId, "Incorrect alarmId " + alarmId);
196 return alarmDao.findAlarmByIdAsync(alarmId.getId()); 197 return alarmDao.findAlarmByIdAsync(alarmId.getId());
@@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.page.TextPageData; @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
35 import org.thingsboard.server.common.data.page.TextPageLink; 35 import org.thingsboard.server.common.data.page.TextPageLink;
36 import org.thingsboard.server.common.data.relation.EntityRelation; 36 import org.thingsboard.server.common.data.relation.EntityRelation;
37 import org.thingsboard.server.dao.customer.CustomerDao; 37 import org.thingsboard.server.dao.customer.CustomerDao;
38 -import org.thingsboard.server.dao.entity.BaseEntityService; 38 +import org.thingsboard.server.dao.entity.AbstractEntityService;
39 import org.thingsboard.server.dao.exception.DataValidationException; 39 import org.thingsboard.server.dao.exception.DataValidationException;
40 import org.thingsboard.server.dao.model.*; 40 import org.thingsboard.server.dao.model.*;
41 import org.thingsboard.server.dao.relation.EntitySearchDirection; 41 import org.thingsboard.server.dao.relation.EntitySearchDirection;
@@ -55,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*; @@ -55,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
55 55
56 @Service 56 @Service
57 @Slf4j 57 @Slf4j
58 -public class BaseAssetService extends BaseEntityService implements AssetService { 58 +public class BaseAssetService extends AbstractEntityService implements AssetService {
59 59
60 @Autowired 60 @Autowired
61 private AssetDao assetDao; 61 private AssetDao assetDao;
@@ -45,7 +45,6 @@ public class ServiceCacheConfiguration { @@ -45,7 +45,6 @@ public class ServiceCacheConfiguration {
45 @Value("${cache.device_credentials.time_to_live}") 45 @Value("${cache.device_credentials.time_to_live}")
46 private Integer cacheDeviceCredentialsTTL; 46 private Integer cacheDeviceCredentialsTTL;
47 47
48 -  
49 @Value("${zk.enabled}") 48 @Value("${zk.enabled}")
50 private boolean zkEnabled; 49 private boolean zkEnabled;
51 @Value("${zk.url}") 50 @Value("${zk.url}")
@@ -31,17 +31,15 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -31,17 +31,15 @@ import com.google.common.util.concurrent.ListenableFuture;
31 import lombok.extern.slf4j.Slf4j; 31 import lombok.extern.slf4j.Slf4j;
32 import org.apache.commons.lang3.StringUtils; 32 import org.apache.commons.lang3.StringUtils;
33 import org.thingsboard.server.common.data.Customer; 33 import org.thingsboard.server.common.data.Customer;
34 -import org.thingsboard.server.common.data.asset.Asset;  
35 import org.thingsboard.server.common.data.id.CustomerId; 34 import org.thingsboard.server.common.data.id.CustomerId;
36 import org.thingsboard.server.common.data.id.TenantId; 35 import org.thingsboard.server.common.data.id.TenantId;
37 import org.thingsboard.server.common.data.page.TextPageData; 36 import org.thingsboard.server.common.data.page.TextPageData;
38 import org.thingsboard.server.common.data.page.TextPageLink; 37 import org.thingsboard.server.common.data.page.TextPageLink;
39 import org.thingsboard.server.dao.dashboard.DashboardService; 38 import org.thingsboard.server.dao.dashboard.DashboardService;
40 import org.thingsboard.server.dao.device.DeviceService; 39 import org.thingsboard.server.dao.device.DeviceService;
41 -import org.thingsboard.server.dao.entity.BaseEntityService; 40 +import org.thingsboard.server.dao.entity.AbstractEntityService;
42 import org.thingsboard.server.dao.exception.DataValidationException; 41 import org.thingsboard.server.dao.exception.DataValidationException;
43 import org.thingsboard.server.dao.exception.IncorrectParameterException; 42 import org.thingsboard.server.dao.exception.IncorrectParameterException;
44 -import org.thingsboard.server.dao.model.AssetEntity;  
45 import org.thingsboard.server.dao.model.CustomerEntity; 43 import org.thingsboard.server.dao.model.CustomerEntity;
46 import org.thingsboard.server.dao.model.TenantEntity; 44 import org.thingsboard.server.dao.model.TenantEntity;
47 import org.thingsboard.server.dao.service.DataValidator; 45 import org.thingsboard.server.dao.service.DataValidator;
@@ -53,7 +51,7 @@ import org.springframework.stereotype.Service; @@ -53,7 +51,7 @@ import org.springframework.stereotype.Service;
53 import org.thingsboard.server.dao.service.Validator; 51 import org.thingsboard.server.dao.service.Validator;
54 @Service 52 @Service
55 @Slf4j 53 @Slf4j
56 -public class CustomerServiceImpl extends BaseEntityService implements CustomerService { 54 +public class CustomerServiceImpl extends AbstractEntityService implements CustomerService {
57 55
58 private static final String PUBLIC_CUSTOMER_TITLE = "Public"; 56 private static final String PUBLIC_CUSTOMER_TITLE = "Public";
59 57
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.dashboard; 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.Dashboard; 19 import org.thingsboard.server.common.data.Dashboard;
19 import org.thingsboard.server.common.data.DashboardInfo; 20 import org.thingsboard.server.common.data.DashboardInfo;
20 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
@@ -27,8 +28,12 @@ public interface DashboardService { @@ -27,8 +28,12 @@ public interface DashboardService {
27 28
28 public Dashboard findDashboardById(DashboardId dashboardId); 29 public Dashboard findDashboardById(DashboardId dashboardId);
29 30
  31 + public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId);
  32 +
30 public DashboardInfo findDashboardInfoById(DashboardId dashboardId); 33 public DashboardInfo findDashboardInfoById(DashboardId dashboardId);
31 34
  35 + public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId);
  36 +
32 public Dashboard saveDashboard(Dashboard dashboard); 37 public Dashboard saveDashboard(Dashboard dashboard);
33 38
34 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId); 39 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
@@ -17,9 +17,13 @@ package org.thingsboard.server.dao.dashboard; @@ -17,9 +17,13 @@ package org.thingsboard.server.dao.dashboard;
17 17
18 import static org.thingsboard.server.dao.DaoUtil.convertDataList; 18 import static org.thingsboard.server.dao.DaoUtil.convertDataList;
19 import static org.thingsboard.server.dao.DaoUtil.getData; 19 import static org.thingsboard.server.dao.DaoUtil.getData;
  20 +import static org.thingsboard.server.dao.service.Validator.validateId;
20 21
21 import java.util.List; 22 import java.util.List;
22 23
  24 +import com.google.common.base.Function;
  25 +import com.google.common.util.concurrent.Futures;
  26 +import com.google.common.util.concurrent.ListenableFuture;
23 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
24 import org.apache.commons.lang3.StringUtils; 28 import org.apache.commons.lang3.StringUtils;
25 import org.thingsboard.server.common.data.Dashboard; 29 import org.thingsboard.server.common.data.Dashboard;
@@ -30,7 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -30,7 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.common.data.page.TextPageData; 34 import org.thingsboard.server.common.data.page.TextPageData;
31 import org.thingsboard.server.common.data.page.TextPageLink; 35 import org.thingsboard.server.common.data.page.TextPageLink;
32 import org.thingsboard.server.dao.customer.CustomerDao; 36 import org.thingsboard.server.dao.customer.CustomerDao;
33 -import org.thingsboard.server.dao.entity.BaseEntityService; 37 +import org.thingsboard.server.dao.entity.AbstractEntityService;
34 import org.thingsboard.server.dao.exception.DataValidationException; 38 import org.thingsboard.server.dao.exception.DataValidationException;
35 import org.thingsboard.server.dao.model.*; 39 import org.thingsboard.server.dao.model.*;
36 import org.thingsboard.server.dao.service.DataValidator; 40 import org.thingsboard.server.dao.service.DataValidator;
@@ -42,7 +46,7 @@ import org.thingsboard.server.dao.service.Validator; @@ -42,7 +46,7 @@ import org.thingsboard.server.dao.service.Validator;
42 46
43 @Service 47 @Service
44 @Slf4j 48 @Slf4j
45 -public class DashboardServiceImpl extends BaseEntityService implements DashboardService { 49 +public class DashboardServiceImpl extends AbstractEntityService implements DashboardService {
46 50
47 @Autowired 51 @Autowired
48 private DashboardDao dashboardDao; 52 private DashboardDao dashboardDao;
@@ -65,6 +69,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard @@ -65,6 +69,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
65 } 69 }
66 70
67 @Override 71 @Override
  72 + public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId) {
  73 + log.trace("Executing findDashboardByIdAsync [{}]", dashboardId);
  74 + validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
  75 + ListenableFuture<DashboardEntity> dashboardEntity = dashboardDao.findByIdAsync(dashboardId.getId());
  76 + return Futures.transform(dashboardEntity, (Function<? super DashboardEntity, ? extends Dashboard>) input -> getData(input));
  77 + }
  78 +
  79 + @Override
68 public DashboardInfo findDashboardInfoById(DashboardId dashboardId) { 80 public DashboardInfo findDashboardInfoById(DashboardId dashboardId) {
69 log.trace("Executing findDashboardInfoById [{}]", dashboardId); 81 log.trace("Executing findDashboardInfoById [{}]", dashboardId);
70 Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 82 Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
@@ -73,6 +85,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard @@ -73,6 +85,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
73 } 85 }
74 86
75 @Override 87 @Override
  88 + public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId) {
  89 + log.trace("Executing findDashboardInfoByIdAsync [{}]", dashboardId);
  90 + validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
  91 + ListenableFuture<DashboardInfoEntity> dashboardInfoEntity = dashboardInfoDao.findByIdAsync(dashboardId.getId());
  92 + return Futures.transform(dashboardInfoEntity, (Function<? super DashboardInfoEntity, ? extends DashboardInfo>) input -> getData(input));
  93 + }
  94 +
  95 + @Override
76 public Dashboard saveDashboard(Dashboard dashboard) { 96 public Dashboard saveDashboard(Dashboard dashboard) {
77 log.trace("Executing saveDashboard [{}]", dashboard); 97 log.trace("Executing saveDashboard [{}]", dashboard);
78 dashboardValidator.validate(dashboard); 98 dashboardValidator.validate(dashboard);
@@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
37 import org.thingsboard.server.common.data.security.DeviceCredentials; 37 import org.thingsboard.server.common.data.security.DeviceCredentials;
38 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 38 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
39 import org.thingsboard.server.dao.customer.CustomerDao; 39 import org.thingsboard.server.dao.customer.CustomerDao;
40 -import org.thingsboard.server.dao.entity.BaseEntityService; 40 +import org.thingsboard.server.dao.entity.AbstractEntityService;
41 import org.thingsboard.server.dao.exception.DataValidationException; 41 import org.thingsboard.server.dao.exception.DataValidationException;
42 import org.thingsboard.server.dao.model.CustomerEntity; 42 import org.thingsboard.server.dao.model.CustomerEntity;
43 import org.thingsboard.server.dao.model.DeviceEntity; 43 import org.thingsboard.server.dao.model.DeviceEntity;
@@ -58,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.*; @@ -58,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
58 58
59 @Service 59 @Service
60 @Slf4j 60 @Slf4j
61 -public class DeviceServiceImpl extends BaseEntityService implements DeviceService { 61 +public class DeviceServiceImpl extends AbstractEntityService implements DeviceService {
62 62
63 @Autowired 63 @Autowired
64 private DeviceDao deviceDao; 64 private DeviceDao deviceDao;
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.dao.entity;
  18 +
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.thingsboard.server.common.data.id.EntityId;
  22 +import org.thingsboard.server.dao.relation.RelationService;
  23 +
  24 +@Slf4j
  25 +public abstract class AbstractEntityService {
  26 +
  27 + @Autowired
  28 + protected RelationService relationService;
  29 +
  30 + protected void deleteEntityRelations(EntityId entityId) {
  31 + log.trace("Executing deleteEntityRelations [{}]", entityId);
  32 + relationService.deleteEntityRelations(entityId);
  33 + }
  34 +
  35 +
  36 +}
@@ -15,23 +15,102 @@ @@ -15,23 +15,102 @@
15 */ 15 */
16 package org.thingsboard.server.dao.entity; 16 package org.thingsboard.server.dao.entity;
17 17
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
18 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
20 -import org.thingsboard.server.common.data.id.EntityId;  
21 -import org.thingsboard.server.dao.relation.RelationService; 23 +import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.*;
  25 +import org.thingsboard.server.common.data.alarm.AlarmId;
  26 +import org.thingsboard.server.common.data.id.*;
  27 +import org.thingsboard.server.dao.alarm.AlarmService;
  28 +import org.thingsboard.server.dao.asset.AssetService;
  29 +import org.thingsboard.server.dao.customer.CustomerService;
  30 +import org.thingsboard.server.dao.dashboard.DashboardService;
  31 +import org.thingsboard.server.dao.device.DeviceService;
  32 +import org.thingsboard.server.dao.plugin.PluginService;
  33 +import org.thingsboard.server.dao.rule.RuleService;
  34 +import org.thingsboard.server.dao.tenant.TenantService;
  35 +import org.thingsboard.server.dao.user.UserService;
22 36
23 /** 37 /**
24 * Created by ashvayka on 04.05.17. 38 * Created by ashvayka on 04.05.17.
25 */ 39 */
  40 +@Service
26 @Slf4j 41 @Slf4j
27 -public class BaseEntityService { 42 +public class BaseEntityService extends AbstractEntityService implements EntityService {
28 43
29 @Autowired 44 @Autowired
30 - protected RelationService relationService; 45 + private AssetService assetService;
31 46
32 - protected void deleteEntityRelations(EntityId entityId) {  
33 - log.trace("Executing deleteEntityRelations [{}]", entityId);  
34 - relationService.deleteEntityRelations(entityId); 47 + @Autowired
  48 + private DeviceService deviceService;
  49 +
  50 + @Autowired
  51 + private RuleService ruleService;
  52 +
  53 + @Autowired
  54 + private PluginService pluginService;
  55 +
  56 + @Autowired
  57 + private TenantService tenantService;
  58 +
  59 + @Autowired
  60 + private CustomerService customerService;
  61 +
  62 + @Autowired
  63 + private UserService userService;
  64 +
  65 + @Autowired
  66 + private DashboardService dashboardService;
  67 +
  68 + @Autowired
  69 + private AlarmService alarmService;
  70 +
  71 + @Override
  72 + public void deleteEntityRelations(EntityId entityId) {
  73 + super.deleteEntityRelations(entityId);
  74 + }
  75 +
  76 + @Override
  77 + public ListenableFuture<String> fetchEntityNameAsync(EntityId entityId) {
  78 + log.trace("Executing fetchEntityNameAsync [{}]", entityId);
  79 + ListenableFuture<String> entityName;
  80 + ListenableFuture<? extends HasName> hasName;
  81 + switch (entityId.getEntityType()) {
  82 + case ASSET:
  83 + hasName = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
  84 + break;
  85 + case DEVICE:
  86 + hasName = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
  87 + break;
  88 + case RULE:
  89 + hasName = ruleService.findRuleByIdAsync(new RuleId(entityId.getId()));
  90 + break;
  91 + case PLUGIN:
  92 + hasName = pluginService.findPluginByIdAsync(new PluginId(entityId.getId()));
  93 + break;
  94 + case TENANT:
  95 + hasName = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
  96 + break;
  97 + case CUSTOMER:
  98 + hasName = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
  99 + break;
  100 + case USER:
  101 + hasName = userService.findUserByIdAsync(new UserId(entityId.getId()));
  102 + break;
  103 + case DASHBOARD:
  104 + hasName = dashboardService.findDashboardInfoByIdAsync(new DashboardId(entityId.getId()));
  105 + break;
  106 + case ALARM:
  107 + hasName = alarmService.findAlarmByIdAsync(new AlarmId(entityId.getId()));
  108 + break;
  109 + default:
  110 + throw new IllegalStateException("Not Implemented!");
  111 + }
  112 + entityName = Futures.transform(hasName, (Function<HasName, String>) hasName1 -> hasName1.getName() );
  113 + return entityName;
35 } 114 }
36 115
37 } 116 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.dao.entity;
  18 +
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import org.thingsboard.server.common.data.id.EntityId;
  21 +
  22 +public interface EntityService {
  23 +
  24 + ListenableFuture<String> fetchEntityNameAsync(EntityId entityId);
  25 +
  26 + void deleteEntityRelations(EntityId entityId);
  27 +
  28 +}
@@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils; @@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils;
22 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
24 import org.thingsboard.server.common.data.id.PluginId; 24 import org.thingsboard.server.common.data.id.PluginId;
25 -import org.thingsboard.server.common.data.id.RuleId;  
26 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.page.TextPageData; 26 import org.thingsboard.server.common.data.page.TextPageData;
28 import org.thingsboard.server.common.data.page.TextPageLink; 27 import org.thingsboard.server.common.data.page.TextPageLink;
@@ -30,9 +29,8 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor; @@ -30,9 +29,8 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
30 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; 29 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
31 import org.thingsboard.server.common.data.plugin.ComponentType; 30 import org.thingsboard.server.common.data.plugin.ComponentType;
32 import org.thingsboard.server.common.data.plugin.PluginMetaData; 31 import org.thingsboard.server.common.data.plugin.PluginMetaData;
33 -import org.thingsboard.server.common.data.rule.RuleMetaData;  
34 import org.thingsboard.server.dao.component.ComponentDescriptorService; 32 import org.thingsboard.server.dao.component.ComponentDescriptorService;
35 -import org.thingsboard.server.dao.entity.BaseEntityService; 33 +import org.thingsboard.server.dao.entity.AbstractEntityService;
36 import org.thingsboard.server.dao.exception.DataValidationException; 34 import org.thingsboard.server.dao.exception.DataValidationException;
37 import org.thingsboard.server.dao.exception.DatabaseException; 35 import org.thingsboard.server.dao.exception.DatabaseException;
38 import org.thingsboard.server.dao.exception.IncorrectParameterException; 36 import org.thingsboard.server.dao.exception.IncorrectParameterException;
@@ -55,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @@ -55,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
55 53
56 @Service 54 @Service
57 @Slf4j 55 @Slf4j
58 -public class BasePluginService extends BaseEntityService implements PluginService { 56 +public class BasePluginService extends AbstractEntityService implements PluginService {
59 57
60 //TODO: move to a better place. 58 //TODO: move to a better place.
61 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); 59 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
@@ -23,9 +23,24 @@ import lombok.extern.slf4j.Slf4j; @@ -23,9 +23,24 @@ import lombok.extern.slf4j.Slf4j;
23 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
25 import org.springframework.util.StringUtils; 25 import org.springframework.util.StringUtils;
  26 +import org.thingsboard.server.common.data.BaseData;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.EntityType;
  29 +import org.thingsboard.server.common.data.asset.Asset;
  30 +import org.thingsboard.server.common.data.id.AssetId;
  31 +import org.thingsboard.server.common.data.id.DeviceId;
26 import org.thingsboard.server.common.data.id.EntityId; 32 import org.thingsboard.server.common.data.id.EntityId;
  33 +import org.thingsboard.server.common.data.id.UUIDBased;
27 import org.thingsboard.server.common.data.relation.EntityRelation; 34 import org.thingsboard.server.common.data.relation.EntityRelation;
  35 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
  36 +import org.thingsboard.server.dao.asset.AssetService;
  37 +import org.thingsboard.server.dao.customer.CustomerService;
  38 +import org.thingsboard.server.dao.device.DeviceService;
  39 +import org.thingsboard.server.dao.entity.EntityService;
28 import org.thingsboard.server.dao.exception.DataValidationException; 40 import org.thingsboard.server.dao.exception.DataValidationException;
  41 +import org.thingsboard.server.dao.plugin.PluginService;
  42 +import org.thingsboard.server.dao.rule.RuleService;
  43 +import org.thingsboard.server.dao.tenant.TenantService;
29 44
30 import javax.annotation.Nullable; 45 import javax.annotation.Nullable;
31 import java.util.*; 46 import java.util.*;
@@ -41,6 +56,9 @@ public class BaseRelationService implements RelationService { @@ -41,6 +56,9 @@ public class BaseRelationService implements RelationService {
41 @Autowired 56 @Autowired
42 private RelationDao relationDao; 57 private RelationDao relationDao;
43 58
  59 + @Autowired
  60 + private EntityService entityService;
  61 +
44 @Override 62 @Override
45 public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) { 63 public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
46 log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType); 64 log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType);
@@ -100,6 +118,31 @@ public class BaseRelationService implements RelationService { @@ -100,6 +118,31 @@ public class BaseRelationService implements RelationService {
100 } 118 }
101 119
102 @Override 120 @Override
  121 + public ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from) {
  122 + log.trace("Executing findInfoByFrom [{}]", from);
  123 + validate(from);
  124 + ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByFrom(from);
  125 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  126 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  127 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  128 + relations1.stream().forEach(relation -> futures.add(fetchRelationInfoAsync(relation)));
  129 + return Futures.successfulAsList(futures);
  130 + });
  131 + return relationsInfo;
  132 + }
  133 +
  134 + private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(EntityRelation relation) {
  135 + ListenableFuture<String> entityName = entityService.fetchEntityNameAsync(relation.getTo());
  136 + ListenableFuture<EntityRelationInfo> entityRelationInfo =
  137 + Futures.transform(entityName, (Function<String, EntityRelationInfo>) entityName1 -> {
  138 + EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation);
  139 + entityRelationInfo1.setToName(entityName1);
  140 + return entityRelationInfo1;
  141 + });
  142 + return entityRelationInfo;
  143 + }
  144 +
  145 + @Override
103 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) { 146 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
104 log.trace("Executing findByFromAndType [{}][{}]", from, relationType); 147 log.trace("Executing findByFromAndType [{}][{}]", from, relationType);
105 validate(from); 148 validate(from);
@@ -18,6 +18,7 @@ package org.thingsboard.server.dao.relation; @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.relation;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.id.EntityId; 19 import org.thingsboard.server.common.data.id.EntityId;
20 import org.thingsboard.server.common.data.relation.EntityRelation; 20 import org.thingsboard.server.common.data.relation.EntityRelation;
  21 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
21 22
22 import java.util.List; 23 import java.util.List;
23 24
@@ -38,6 +39,8 @@ public interface RelationService { @@ -38,6 +39,8 @@ public interface RelationService {
38 39
39 ListenableFuture<List<EntityRelation>> findByFrom(EntityId from); 40 ListenableFuture<List<EntityRelation>> findByFrom(EntityId from);
40 41
  42 + ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from);
  43 +
41 ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType); 44 ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
42 45
43 ListenableFuture<List<EntityRelation>> findByTo(EntityId to); 46 ListenableFuture<List<EntityRelation>> findByTo(EntityId to);
@@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j;
23 import org.apache.commons.lang3.StringUtils; 23 import org.apache.commons.lang3.StringUtils;
24 import org.springframework.beans.factory.annotation.Autowired; 24 import org.springframework.beans.factory.annotation.Autowired;
25 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
26 -import org.thingsboard.server.common.data.asset.Asset;  
27 import org.thingsboard.server.common.data.id.RuleId; 26 import org.thingsboard.server.common.data.id.RuleId;
28 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
29 import org.thingsboard.server.common.data.page.TextPageData; 28 import org.thingsboard.server.common.data.page.TextPageData;
@@ -34,11 +33,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -34,11 +33,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
34 import org.thingsboard.server.common.data.plugin.PluginMetaData; 33 import org.thingsboard.server.common.data.plugin.PluginMetaData;
35 import org.thingsboard.server.common.data.rule.RuleMetaData; 34 import org.thingsboard.server.common.data.rule.RuleMetaData;
36 import org.thingsboard.server.dao.component.ComponentDescriptorService; 35 import org.thingsboard.server.dao.component.ComponentDescriptorService;
37 -import org.thingsboard.server.dao.entity.BaseEntityService; 36 +import org.thingsboard.server.dao.entity.AbstractEntityService;
38 import org.thingsboard.server.dao.exception.DataValidationException; 37 import org.thingsboard.server.dao.exception.DataValidationException;
39 import org.thingsboard.server.dao.exception.DatabaseException; 38 import org.thingsboard.server.dao.exception.DatabaseException;
40 import org.thingsboard.server.dao.exception.IncorrectParameterException; 39 import org.thingsboard.server.dao.exception.IncorrectParameterException;
41 -import org.thingsboard.server.dao.model.AssetEntity;  
42 import org.thingsboard.server.dao.model.RuleMetaDataEntity; 40 import org.thingsboard.server.dao.model.RuleMetaDataEntity;
43 import org.thingsboard.server.dao.plugin.PluginService; 41 import org.thingsboard.server.dao.plugin.PluginService;
44 import org.thingsboard.server.dao.service.DataValidator; 42 import org.thingsboard.server.dao.service.DataValidator;
@@ -58,7 +56,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -58,7 +56,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
58 56
59 @Service 57 @Service
60 @Slf4j 58 @Slf4j
61 -public class BaseRuleService extends BaseEntityService implements RuleService { 59 +public class BaseRuleService extends AbstractEntityService implements RuleService {
62 60
63 private final TenantId systemTenantId = new TenantId(NULL_UUID); 61 private final TenantId systemTenantId = new TenantId(NULL_UUID);
64 62
@@ -26,7 +26,6 @@ import com.google.common.util.concurrent.Futures; @@ -26,7 +26,6 @@ import com.google.common.util.concurrent.Futures;
26 import com.google.common.util.concurrent.ListenableFuture; 26 import com.google.common.util.concurrent.ListenableFuture;
27 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
28 import org.apache.commons.lang3.StringUtils; 28 import org.apache.commons.lang3.StringUtils;
29 -import org.thingsboard.server.common.data.Customer;  
30 import org.thingsboard.server.common.data.Tenant; 29 import org.thingsboard.server.common.data.Tenant;
31 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
32 import org.thingsboard.server.common.data.page.TextPageData; 31 import org.thingsboard.server.common.data.page.TextPageData;
@@ -34,9 +33,8 @@ import org.thingsboard.server.common.data.page.TextPageLink; @@ -34,9 +33,8 @@ import org.thingsboard.server.common.data.page.TextPageLink;
34 import org.thingsboard.server.dao.customer.CustomerService; 33 import org.thingsboard.server.dao.customer.CustomerService;
35 import org.thingsboard.server.dao.dashboard.DashboardService; 34 import org.thingsboard.server.dao.dashboard.DashboardService;
36 import org.thingsboard.server.dao.device.DeviceService; 35 import org.thingsboard.server.dao.device.DeviceService;
37 -import org.thingsboard.server.dao.entity.BaseEntityService; 36 +import org.thingsboard.server.dao.entity.AbstractEntityService;
38 import org.thingsboard.server.dao.exception.DataValidationException; 37 import org.thingsboard.server.dao.exception.DataValidationException;
39 -import org.thingsboard.server.dao.model.CustomerEntity;  
40 import org.thingsboard.server.dao.model.TenantEntity; 38 import org.thingsboard.server.dao.model.TenantEntity;
41 import org.thingsboard.server.dao.plugin.PluginService; 39 import org.thingsboard.server.dao.plugin.PluginService;
42 import org.thingsboard.server.dao.rule.RuleService; 40 import org.thingsboard.server.dao.rule.RuleService;
@@ -50,7 +48,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -50,7 +48,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
50 48
51 @Service 49 @Service
52 @Slf4j 50 @Slf4j
53 -public class TenantServiceImpl extends BaseEntityService implements TenantService { 51 +public class TenantServiceImpl extends AbstractEntityService implements TenantService {
54 52
55 private static final String DEFAULT_TENANT_REGION = "Global"; 53 private static final String DEFAULT_TENANT_REGION = "Global";
56 54
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.dao.user; 16 package org.thingsboard.server.dao.user;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 import org.thingsboard.server.common.data.User; 19 import org.thingsboard.server.common.data.User;
19 import org.thingsboard.server.common.data.id.CustomerId; 20 import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
@@ -27,6 +28,8 @@ public interface UserService { @@ -27,6 +28,8 @@ public interface UserService {
27 28
28 public User findUserById(UserId userId); 29 public User findUserById(UserId userId);
29 30
  31 + public ListenableFuture<User> findUserByIdAsync(UserId userId);
  32 +
30 public User findUserByEmail(String email); 33 public User findUserByEmail(String email);
31 34
32 public User saveUser(User user); 35 public User saveUser(User user);
@@ -23,6 +23,9 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @@ -23,6 +23,9 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
23 23
24 import java.util.List; 24 import java.util.List;
25 25
  26 +import com.google.common.base.Function;
  27 +import com.google.common.util.concurrent.Futures;
  28 +import com.google.common.util.concurrent.ListenableFuture;
26 import lombok.extern.slf4j.Slf4j; 29 import lombok.extern.slf4j.Slf4j;
27 import org.apache.commons.lang3.RandomStringUtils; 30 import org.apache.commons.lang3.RandomStringUtils;
28 import org.apache.commons.lang3.StringUtils; 31 import org.apache.commons.lang3.StringUtils;
@@ -35,7 +38,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; @@ -35,7 +38,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
35 import org.thingsboard.server.common.data.security.Authority; 38 import org.thingsboard.server.common.data.security.Authority;
36 import org.thingsboard.server.common.data.security.UserCredentials; 39 import org.thingsboard.server.common.data.security.UserCredentials;
37 import org.thingsboard.server.dao.customer.CustomerDao; 40 import org.thingsboard.server.dao.customer.CustomerDao;
38 -import org.thingsboard.server.dao.entity.BaseEntityService; 41 +import org.thingsboard.server.dao.entity.AbstractEntityService;
39 import org.thingsboard.server.dao.exception.DataValidationException; 42 import org.thingsboard.server.dao.exception.DataValidationException;
40 import org.thingsboard.server.dao.exception.IncorrectParameterException; 43 import org.thingsboard.server.dao.exception.IncorrectParameterException;
41 import org.thingsboard.server.dao.model.*; 44 import org.thingsboard.server.dao.model.*;
@@ -47,7 +50,7 @@ import org.springframework.stereotype.Service; @@ -47,7 +50,7 @@ import org.springframework.stereotype.Service;
47 50
48 @Service 51 @Service
49 @Slf4j 52 @Slf4j
50 -public class UserServiceImpl extends BaseEntityService implements UserService { 53 +public class UserServiceImpl extends AbstractEntityService implements UserService {
51 54
52 @Autowired 55 @Autowired
53 private UserDao userDao; 56 private UserDao userDao;
@@ -78,6 +81,14 @@ public class UserServiceImpl extends BaseEntityService implements UserService { @@ -78,6 +81,14 @@ public class UserServiceImpl extends BaseEntityService implements UserService {
78 } 81 }
79 82
80 @Override 83 @Override
  84 + public ListenableFuture<User> findUserByIdAsync(UserId userId) {
  85 + log.trace("Executing findUserByIdAsync [{}]", userId);
  86 + validateId(userId, "Incorrect userId " + userId);
  87 + ListenableFuture<UserEntity> userEntity = userDao.findByIdAsync(userId.getId());
  88 + return Futures.transform(userEntity, (Function<? super UserEntity, ? extends User>) input -> getData(input));
  89 + }
  90 +
  91 + @Override
81 public User saveUser(User user) { 92 public User saveUser(User user) {
82 log.trace("Executing saveUser [{}]", user); 93 log.trace("Executing saveUser [{}]", user);
83 userValidator.validate(user); 94 userValidator.validate(user);
@@ -95,7 +95,7 @@ public class AlarmServiceTest extends AbstractServiceTest { @@ -95,7 +95,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
95 Assert.assertEquals(0L, created.getAckTs()); 95 Assert.assertEquals(0L, created.getAckTs());
96 Assert.assertEquals(0L, created.getClearTs()); 96 Assert.assertEquals(0L, created.getClearTs());
97 97
98 - Alarm fetched = alarmService.findAlarmById(created.getId()).get(); 98 + Alarm fetched = alarmService.findAlarmByIdAsync(created.getId()).get();
99 Assert.assertEquals(created, fetched); 99 Assert.assertEquals(created, fetched);
100 } 100 }
101 101
@@ -137,7 +137,7 @@ public class AlarmServiceTest extends AbstractServiceTest { @@ -137,7 +137,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
137 Assert.assertEquals(created, alarms.getData().get(0)); 137 Assert.assertEquals(created, alarms.getData().get(0));
138 138
139 alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get(); 139 alarmService.ackAlarm(created.getId(), System.currentTimeMillis()).get();
140 - created = alarmService.findAlarmById(created.getId()).get(); 140 + created = alarmService.findAlarmByIdAsync(created.getId()).get();
141 141
142 alarms = alarmService.findAlarms(AlarmQuery.builder() 142 alarms = alarmService.findAlarms(AlarmQuery.builder()
143 .affectedEntityId(childId) 143 .affectedEntityId(childId)
@@ -158,7 +158,7 @@ public class AlarmServiceTest extends AbstractServiceTest { @@ -158,7 +158,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
158 Assert.assertEquals(0, alarms.getData().size()); 158 Assert.assertEquals(0, alarms.getData().size());
159 159
160 alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get(); 160 alarmService.clearAlarm(created.getId(), System.currentTimeMillis()).get();
161 - created = alarmService.findAlarmById(created.getId()).get(); 161 + created = alarmService.findAlarmByIdAsync(created.getId()).get();
162 162
163 alarms = alarmService.findAlarms(AlarmQuery.builder() 163 alarms = alarmService.findAlarms(AlarmQuery.builder()
164 .affectedEntityId(childId) 164 .affectedEntityId(childId)
1 CASSANDRA_DATA_DIR=/home/docker/cassandra_volume 1 CASSANDRA_DATA_DIR=/home/docker/cassandra_volume
  2 +
  3 +# cassandra schema container environment variables
  4 +CREATE_SCHEMA=true
  5 +ADD_SYSTEM_DATA=false
  6 +ADD_DEMO_DATA=false
  7 +CASSANDRA_URL=cassandra
  1 +#
  2 +# Copyright © 2016-2017 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +apiVersion: v1
  18 +kind: Service
  19 +metadata:
  20 + name: cassandra-headless
  21 + labels:
  22 + app: cassandra-headless
  23 +spec:
  24 + ports:
  25 + - port: 9042
  26 + name: cql
  27 + clusterIP: None
  28 + selector:
  29 + app: cassandra
  30 +---
  31 +apiVersion: "apps/v1beta1"
  32 +kind: StatefulSet
  33 +metadata:
  34 + name: cassandra
  35 +spec:
  36 + serviceName: cassandra-headless
  37 + replicas: 2
  38 + template:
  39 + metadata:
  40 + labels:
  41 + app: cassandra
  42 + spec:
  43 + nodeSelector:
  44 + machinetype: other
  45 + affinity:
  46 + podAntiAffinity:
  47 + requiredDuringSchedulingIgnoredDuringExecution:
  48 + - labelSelector:
  49 + matchExpressions:
  50 + - key: "app"
  51 + operator: In
  52 + values:
  53 + - cassandra-headless
  54 + topologyKey: "kubernetes.io/hostname"
  55 + containers:
  56 + - name: cassandra
  57 + image: cassandra:3.9
  58 + imagePullPolicy: Always
  59 + ports:
  60 + - containerPort: 7000
  61 + name: intra-node
  62 + - containerPort: 7001
  63 + name: tls-intra-node
  64 + - containerPort: 7199
  65 + name: jmx
  66 + - containerPort: 9042
  67 + name: cql
  68 + - containerPort: 9160
  69 + name: thrift
  70 + securityContext:
  71 + capabilities:
  72 + add:
  73 + - IPC_LOCK
  74 + lifecycle:
  75 + preStop:
  76 + exec:
  77 + command: ["/bin/sh", "-c", "PID=$(pidof java) && kill $PID && while ps -p $PID > /dev/null; do sleep 1; done"]
  78 + env:
  79 + - name: MAX_HEAP_SIZE
  80 + value: 2048M
  81 + - name: HEAP_NEWSIZE
  82 + value: 100M
  83 + - name: CASSANDRA_SEEDS
  84 + value: "cassandra-0.cassandra-headless.default.svc.cluster.local"
  85 + - name: CASSANDRA_CLUSTER_NAME
  86 + value: "Thingsboard-Cluster"
  87 + - name: CASSANDRA_DC
  88 + value: "DC1-Thingsboard-Cluster"
  89 + - name: CASSANDRA_RACK
  90 + value: "Rack-Thingsboard-Cluster"
  91 + - name: CASSANDRA_AUTO_BOOTSTRAP
  92 + value: "false"
  93 + - name: POD_IP
  94 + valueFrom:
  95 + fieldRef:
  96 + fieldPath: status.podIP
  97 + - name: POD_NAMESPACE
  98 + valueFrom:
  99 + fieldRef:
  100 + fieldPath: metadata.namespace
  101 + readinessProbe:
  102 + exec:
  103 + command:
  104 + - /bin/bash
  105 + - -c
  106 + - /ready-probe.sh
  107 + initialDelaySeconds: 15
  108 + timeoutSeconds: 5
  109 + volumeMounts:
  110 + - name: cassandra-data
  111 + mountPath: /var/lib/cassandra/data
  112 + - name: cassandra-commitlog
  113 + mountPath: /var/lib/cassandra/commitlog
  114 + volumeClaimTemplates:
  115 + - metadata:
  116 + name: cassandra-data
  117 + annotations:
  118 + volume.beta.kubernetes.io/storage-class: fast
  119 + spec:
  120 + accessModes: [ "ReadWriteOnce" ]
  121 + resources:
  122 + requests:
  123 + storage: 3Gi
  124 + - metadata:
  125 + name: cassandra-commitlog
  126 + annotations:
  127 + volume.beta.kubernetes.io/storage-class: fast
  128 + spec:
  129 + accessModes: [ "ReadWriteOnce" ]
  130 + resources:
  131 + requests:
  132 + storage: 2Gi
docker/common/common.yaml renamed from docker/docker-compose.random.yml
@@ -14,13 +14,20 @@ @@ -14,13 +14,20 @@
14 # limitations under the License. 14 # limitations under the License.
15 # 15 #
16 16
17 -version: '2'  
18 -  
19 -services:  
20 - db:  
21 - ports:  
22 - - "9042"  
23 - - "9160"  
24 - zk:  
25 - ports:  
26 - - "2181" 17 +---
  18 +apiVersion: storage.k8s.io/v1beta1
  19 +kind: StorageClass
  20 +metadata:
  21 + name: slow
  22 +provisioner: kubernetes.io/gce-pd
  23 +parameters:
  24 + type: pd-standard
  25 +---
  26 +apiVersion: storage.k8s.io/v1beta1
  27 +kind: StorageClass
  28 +metadata:
  29 + name: fast
  30 +provisioner: kubernetes.io/gce-pd
  31 +parameters:
  32 + type: pd-ssd
  33 +---
1 -#!/bin/bash  
2 -#  
3 -# Copyright © 2016-2017 The Thingsboard Authors  
4 -#  
5 -# Licensed under the Apache License, Version 2.0 (the "License");  
6 -# you may not use this file except in compliance with the License.  
7 -# You may obtain a copy of the License at  
8 -#  
9 -# http://www.apache.org/licenses/LICENSE-2.0  
10 -#  
11 -# Unless required by applicable law or agreed to in writing, software  
12 -# distributed under the License is distributed on an "AS IS" BASIS,  
13 -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 -# See the License for the specific language governing permissions and  
15 -# limitations under the License.  
16 -#  
17 -  
18 -  
19 -command='docker-compose -f docker-compose.yml -f docker-compose.random.yml'  
20 -  
21 -echo "stopping images.."  
22 -$command stop  
23 -  
24 -echo "removing stopped images.."  
25 -$command rm -f  
26 -  
27 -echo "building images.."  
28 -$command build  
29 -  
30 -echo "starting images..."  
31 -$command up -d  
1 -#!/bin/bash  
2 -#  
3 -# Copyright © 2016-2017 The Thingsboard Authors  
4 -#  
5 -# Licensed under the Apache License, Version 2.0 (the "License");  
6 -# you may not use this file except in compliance with the License.  
7 -# You may obtain a copy of the License at  
8 -#  
9 -# http://www.apache.org/licenses/LICENSE-2.0  
10 -#  
11 -# Unless required by applicable law or agreed to in writing, software  
12 -# distributed under the License is distributed on an "AS IS" BASIS,  
13 -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 -# See the License for the specific language governing permissions and  
15 -# limitations under the License.  
16 -#  
17 -  
18 -  
19 -command='docker-compose -f docker-compose.yml -f docker-compose.static.yml'  
20 -  
21 -echo "stopping images.."  
22 -$command stop  
23 -  
24 -echo "removing stopped images.."  
25 -$command rm -f  
26 -  
27 -echo "building images.."  
28 -$command build  
29 -  
30 -echo "starting cassandra, zookeeper, thingsboard-db-schema images..."  
31 -$command up -d db zk thingsboard-db-schema  
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 version: '2' 17 version: '2'
18 18
19 services: 19 services:
20 - db: 20 + cassandra:
21 ports: 21 ports:
22 - "9042:9042" 22 - "9042:9042"
23 - "9160:9160" 23 - "9160:9160"
@@ -17,24 +17,32 @@ @@ -17,24 +17,32 @@
17 version: '2' 17 version: '2'
18 18
19 services: 19 services:
20 - thingsboard:  
21 - image: "thingsboard/application:1.2.3" 20 + tb:
  21 + image: "thingsboard/application:1.2.4"
22 ports: 22 ports:
23 - "8080:8080" 23 - "8080:8080"
24 - "1883:1883" 24 - "1883:1883"
25 - "5683:5683/udp" 25 - "5683:5683/udp"
26 env_file: 26 env_file:
27 - - thingsboard.env  
28 - entrypoint: ./run_thingsboard.sh  
29 - thingsboard-db-schema:  
30 - image: "thingsboard/thingsboard-db-schema:1.2.3"  
31 - env_file:  
32 - - thingsboard-db-schema.env  
33 - entrypoint: ./install_schema.sh  
34 - db: 27 + - tb.env
  28 + entrypoint: ./run-application.sh
  29 + tb-cassandra-schema:
  30 + image: "thingsboard/tb-cassandra-schema:1.2.4"
  31 + environment:
  32 + - CREATE_SCHEMA=${CREATE_SCHEMA}
  33 + - ADD_SYSTEM_DATA=${ADD_SYSTEM_DATA}
  34 + - ADD_DEMO_DATA=${ADD_DEMO_DATA}
  35 + - CASSANDRA_URL=${CASSANDRA_URL}
  36 + entrypoint: ./install-schema.sh
  37 + cassandra:
35 image: "cassandra:3.9" 38 image: "cassandra:3.9"
  39 + ports:
  40 + - "9042"
  41 + - "9160"
36 volumes: 42 volumes:
37 - "${CASSANDRA_DATA_DIR}:/var/lib/cassandra" 43 - "${CASSANDRA_DATA_DIR}:/var/lib/cassandra"
38 zk: 44 zk:
39 image: "zookeeper:3.4.9" 45 image: "zookeeper:3.4.9"
  46 + ports:
  47 + - "2181"
40 restart: always 48 restart: always
docker/tb-cassandra-schema/Dockerfile renamed from docker/thingsboard-db-schema/Dockerfile
@@ -16,12 +16,12 @@ @@ -16,12 +16,12 @@
16 16
17 FROM cassandra:3.9 17 FROM cassandra:3.9
18 18
19 -ADD install_schema.sh /root/install_schema.sh 19 +ADD install-schema.sh /root/install-schema.sh
20 20
21 RUN apt-get update \ 21 RUN apt-get update \
22 && apt-get install -y nmap 22 && apt-get install -y nmap
23 23
24 -RUN chmod +x /root/install_schema.sh 24 +RUN chmod +x /root/install-schema.sh
25 25
26 ADD schema.cql /root/schema.cql 26 ADD schema.cql /root/schema.cql
27 ADD demo-data.cql /root/demo-data.cql 27 ADD demo-data.cql /root/demo-data.cql
  1 +VERSION=1.2.4
  2 +PROJECT=thingsboard
  3 +APP=tb-cassandra-schema
  4 +
  5 +build:
  6 + cp ../../dao/src/main/resources/schema.cql .
  7 + cp ../../dao/src/main/resources/demo-data.cql .
  8 + cp ../../dao/src/main/resources/system-data.cql .
  9 + docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
  10 + rm schema.cql demo-data.cql system-data.cql
  11 +
  12 +push: build
  13 + docker push ${PROJECT}/${APP}:${VERSION}
docker/tb-cassandra-schema/install-schema.sh renamed from docker/thingsboard-db-schema/install_schema.sh
@@ -16,15 +16,15 @@ @@ -16,15 +16,15 @@
16 # 16 #
17 17
18 18
19 -until nmap db -p 9042 | grep "9042/tcp open" 19 +until nmap $CASSANDRA_URL -p 9042 | grep "9042/tcp open"
20 do 20 do
21 - echo "Wait for Cassandra..." 21 + echo "Wait for $CASSANDRA_URL..."
22 sleep 10 22 sleep 10
23 done 23 done
24 24
25 -if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then 25 +if [ "$CREATE_SCHEMA" == "true" ]; then
26 echo "Creating 'Thingsboard' keyspace..." 26 echo "Creating 'Thingsboard' keyspace..."
27 - cqlsh db -f /root/schema.cql 27 + cqlsh $CASSANDRA_URL -f /root/schema.cql
28 if [ "$?" -eq 0 ]; then 28 if [ "$?" -eq 0 ]; then
29 echo "'Thingsboard' keyspace was successfully created!" 29 echo "'Thingsboard' keyspace was successfully created!"
30 else 30 else
@@ -32,9 +32,9 @@ if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then @@ -32,9 +32,9 @@ if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then
32 fi 32 fi
33 fi 33 fi
34 34
35 -if [ "$SKIP_SYSTEM_DATA" == "false" ]; then 35 +if [ "$ADD_SYSTEM_DATA" == "true" ]; then
36 echo "Adding system data..." 36 echo "Adding system data..."
37 - cqlsh db -f /root/system-data.cql 37 + cqlsh $CASSANDRA_URL -f /root/system-data.cql
38 if [ "$?" -eq 0 ]; then 38 if [ "$?" -eq 0 ]; then
39 echo "System data was successfully added!" 39 echo "System data was successfully added!"
40 else 40 else
@@ -42,9 +42,9 @@ if [ "$SKIP_SYSTEM_DATA" == "false" ]; then @@ -42,9 +42,9 @@ if [ "$SKIP_SYSTEM_DATA" == "false" ]; then
42 fi 42 fi
43 fi 43 fi
44 44
45 -if [ "$SKIP_DEMO_DATA" == "false" ]; then 45 +if [ "$ADD_DEMO_DATA" == "true" ]; then
46 echo "Adding demo data..." 46 echo "Adding demo data..."
47 - cqlsh db -f /root/demo-data.cql 47 + cqlsh $CASSANDRA_URL -f /root/demo-data.cql
48 if [ "$?" -eq 0 ]; then 48 if [ "$?" -eq 0 ]; then
49 echo "Demo data was successfully added!" 49 echo "Demo data was successfully added!"
50 else 50 else
  1 +#
  2 +# Copyright © 2016-2017 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +apiVersion: v1
  18 +kind: Pod
  19 +metadata:
  20 + name: tb-cassandra-schema
  21 +spec:
  22 + containers:
  23 + - name: tb-cassandra-schema
  24 + imagePullPolicy: Always
  25 + image: thingsboard/tb-cassandra-schema:1.2.4
  26 + env:
  27 + - name: CREATE_SCHEMA
  28 + value: "false"
  29 + - name: ADD_SYSTEM_DATA
  30 + value: "false"
  31 + - name : ADD_DEMO_DATA
  32 + value: "false"
  33 + - name : CASSANDRA_URL
  34 + value: "cassandra-headless"
  35 + command:
  36 + - sh
  37 + - -c
  38 + - ./install-schema.sh
  39 + restartPolicy: Never
docker/tb.env renamed from docker/thingsboard.env
1 #Thingsboard server configuration 1 #Thingsboard server configuration
2 2
3 -CASSANDRA_URL=db:9042 3 +TB_CASSANDRA_SCHEMA_URL=tb-cassandra-schema
  4 +CASSANDRA_URL=cassandra:9042
4 ZOOKEEPER_URL=zk:2181 5 ZOOKEEPER_URL=zk:2181
5 MQTT_BIND_ADDRESS=0.0.0.0 6 MQTT_BIND_ADDRESS=0.0.0.0
6 MQTT_BIND_PORT=1883 7 MQTT_BIND_PORT=1883
docker/tb/Dockerfile renamed from docker/thingsboard/Dockerfile
@@ -16,9 +16,9 @@ @@ -16,9 +16,9 @@
16 16
17 FROM openjdk:8-jre 17 FROM openjdk:8-jre
18 18
19 -ADD run_thingsboard.sh /root/run_thingsboard.sh 19 +ADD run-application.sh /root/run-application.sh
20 ADD thingsboard.deb /root/thingsboard.deb 20 ADD thingsboard.deb /root/thingsboard.deb
21 21
22 -RUN chmod +x /root/run_thingsboard.sh 22 +RUN chmod +x /root/run-application.sh
23 23
24 WORKDIR /root 24 WORKDIR /root
  1 +VERSION=1.2.4
  2 +PROJECT=thingsboard
  3 +APP=application
  4 +
  5 +build:
  6 + cp ../../application/target/thingsboard.deb .
  7 + docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
  8 + rm thingsboard.deb
  9 +
  10 +push: build
  11 + docker push ${PROJECT}/${APP}:${VERSION}
docker/tb/run-application.sh renamed from docker/thingsboard/run_thingsboard.sh
@@ -21,12 +21,12 @@ dpkg -i /root/thingsboard.deb @@ -21,12 +21,12 @@ dpkg -i /root/thingsboard.deb
21 reachable=0 21 reachable=0
22 while [ $reachable -eq 0 ]; 22 while [ $reachable -eq 0 ];
23 do 23 do
24 - echo "thingsboard-db-schema container is still in progress. waiting until it completed..." 24 + echo "$TB_CASSANDRA_SCHEMA_URL container is still in progress. waiting until it completed..."
25 sleep 3 25 sleep 3
26 - ping -q -c 1 thingsboard-db-schema > /dev/null 2>&1 26 + ping -q -c 1 $TB_CASSANDRA_SCHEMA_URL > /dev/null 2>&1
27 if [ "$?" -ne 0 ]; 27 if [ "$?" -ne 0 ];
28 then 28 then
29 - echo "thingsboard-db-schema container completed!" 29 + echo "$TB_CASSANDRA_SCHEMA_URL container completed!"
30 reachable=1 30 reachable=1
31 fi 31 fi
32 done 32 done
  1 +#
  2 +# Copyright © 2016-2017 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +---
  18 +apiVersion: v1
  19 +kind: Service
  20 +metadata:
  21 + name: tb-service
  22 + labels:
  23 + app: tb-service
  24 +spec:
  25 + ports:
  26 + - port: 8080
  27 + name: ui
  28 + - port: 1883
  29 + name: mqtt
  30 + - port: 5683
  31 + name: coap
  32 + selector:
  33 + app: tb
  34 + type: LoadBalancer
  35 +---
  36 +apiVersion: policy/v1beta1
  37 +kind: PodDisruptionBudget
  38 +metadata:
  39 + name: tb-budget
  40 +spec:
  41 + selector:
  42 + matchLabels:
  43 + app: tb
  44 + minAvailable: 3
  45 +---
  46 +apiVersion: v1
  47 +kind: ConfigMap
  48 +metadata:
  49 + name: tb-config
  50 +data:
  51 + zookeeper.enabled: "true"
  52 + zookeeper.url: "zk-headless"
  53 + cassandra.url: "cassandra-headless:9042"
  54 +---
  55 +apiVersion: apps/v1beta1
  56 +kind: StatefulSet
  57 +metadata:
  58 + name: tb
  59 +spec:
  60 + serviceName: "tb-service"
  61 + replicas: 3
  62 + template:
  63 + metadata:
  64 + labels:
  65 + app: tb
  66 + spec:
  67 + nodeSelector:
  68 + machinetype: tb
  69 + affinity:
  70 + podAntiAffinity:
  71 + requiredDuringSchedulingIgnoredDuringExecution:
  72 + - labelSelector:
  73 + matchExpressions:
  74 + - key: "app"
  75 + operator: In
  76 + values:
  77 + - tb-service
  78 + topologyKey: "kubernetes.io/hostname"
  79 + containers:
  80 + - name: tb
  81 + imagePullPolicy: Always
  82 + image: thingsboard/application:1.2.4
  83 + ports:
  84 + - containerPort: 8080
  85 + name: ui
  86 + - containerPort: 1883
  87 + name: mqtt
  88 + - containerPort: 5683
  89 + name: coap
  90 + - containerPort: 9001
  91 + name: rpc
  92 + env:
  93 + - name: ZOOKEEPER_ENABLED
  94 + valueFrom:
  95 + configMapKeyRef:
  96 + name: tb-config
  97 + key: zookeeper.enabled
  98 + - name: ZOOKEEPER_URL
  99 + valueFrom:
  100 + configMapKeyRef:
  101 + name: tb-config
  102 + key: zookeeper.url
  103 + - name : CASSANDRA_URL
  104 + valueFrom:
  105 + configMapKeyRef:
  106 + name: tb-config
  107 + key: cassandra.url
  108 + - name : RPC_HOST
  109 + valueFrom:
  110 + fieldRef:
  111 + fieldPath: status.podIP
  112 + command:
  113 + - sh
  114 + - -c
  115 + - ./run-application.sh
  116 + livenessProbe:
  117 + httpGet:
  118 + path: /login
  119 + port: ui-port
  120 + initialDelaySeconds: 120
  121 + timeoutSeconds: 10
1 -#Db schema configuration  
2 -  
3 -SKIP_SCHEMA_CREATION=false  
4 -SKIP_SYSTEM_DATA=false  
5 -SKIP_DEMO_DATA=false  
1 -#!/bin/bash  
2 -#  
3 -# Copyright © 2016-2017 The Thingsboard Authors  
4 -#  
5 -# Licensed under the Apache License, Version 2.0 (the "License");  
6 -# you may not use this file except in compliance with the License.  
7 -# You may obtain a copy of the License at  
8 -#  
9 -# http://www.apache.org/licenses/LICENSE-2.0  
10 -#  
11 -# Unless required by applicable law or agreed to in writing, software  
12 -# distributed under the License is distributed on an "AS IS" BASIS,  
13 -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 -# See the License for the specific language governing permissions and  
15 -# limitations under the License.  
16 -#  
17 -  
18 -  
19 -cp ../../dao/src/main/resources/schema.cql schema.cql  
20 -cp ../../dao/src/main/resources/demo-data.cql demo-data.cql  
21 -cp ../../dao/src/main/resources/system-data.cql system-data.cql  
22 -  
23 -docker build -t thingsboard/thingsboard-db-schema:1.2.3 -t thingsboard/thingsboard-db-schema:latest .  
24 -  
25 -docker login  
26 -  
27 -docker push thingsboard/thingsboard-db-schema:1.2.3  
28 -docker push thingsboard/thingsboard-db-schema:latest  
  1 +#
  2 +# Copyright © 2016-2017 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +FROM ubuntu:16.04
  18 +ENV ZK_USER=zookeeper \
  19 +ZK_DATA_DIR=/var/lib/zookeeper/data \
  20 +ZK_DATA_LOG_DIR=/var/lib/zookeeper/log \
  21 +ZK_LOG_DIR=/var/log/zookeeper \
  22 +JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
  23 +
  24 +ARG GPG_KEY=C823E3E5B12AF29C67F81976F5CECB3CB5E9BD2D
  25 +ARG ZK_DIST=zookeeper-3.4.9
  26 +RUN set -x \
  27 + && apt-get update \
  28 + && apt-get install -y openjdk-8-jre-headless wget netcat-openbsd \
  29 + && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz" \
  30 + && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz.asc" \
  31 + && export GNUPGHOME="$(mktemp -d)" \
  32 + && gpg --keyserver ha.pool.sks-keyservers.net --recv-key "$GPG_KEY" \
  33 + && gpg --batch --verify "$ZK_DIST.tar.gz.asc" "$ZK_DIST.tar.gz" \
  34 + && tar -xzf "$ZK_DIST.tar.gz" -C /opt \
  35 + && rm -r "$GNUPGHOME" "$ZK_DIST.tar.gz" "$ZK_DIST.tar.gz.asc" \
  36 + && ln -s /opt/$ZK_DIST /opt/zookeeper \
  37 + && rm -rf /opt/zookeeper/CHANGES.txt \
  38 + /opt/zookeeper/README.txt \
  39 + /opt/zookeeper/NOTICE.txt \
  40 + /opt/zookeeper/CHANGES.txt \
  41 + /opt/zookeeper/README_packaging.txt \
  42 + /opt/zookeeper/build.xml \
  43 + /opt/zookeeper/config \
  44 + /opt/zookeeper/contrib \
  45 + /opt/zookeeper/dist-maven \
  46 + /opt/zookeeper/docs \
  47 + /opt/zookeeper/ivy.xml \
  48 + /opt/zookeeper/ivysettings.xml \
  49 + /opt/zookeeper/recipes \
  50 + /opt/zookeeper/src \
  51 + /opt/zookeeper/$ZK_DIST.jar.asc \
  52 + /opt/zookeeper/$ZK_DIST.jar.md5 \
  53 + /opt/zookeeper/$ZK_DIST.jar.sha1 \
  54 + && apt-get autoremove -y wget \
  55 + && rm -rf /var/lib/apt/lists/*
  56 +
  57 +#Copy configuration generator script to bin
  58 +COPY zk-gen-config.sh zk-ok.sh /opt/zookeeper/bin/
  59 +
  60 +# Create a user for the zookeeper process and configure file system ownership
  61 +# for nessecary directories and symlink the distribution as a user executable
  62 +RUN set -x \
  63 + && useradd $ZK_USER \
  64 + && [ `id -u $ZK_USER` -eq 1000 ] \
  65 + && [ `id -g $ZK_USER` -eq 1000 ] \
  66 + && mkdir -p $ZK_DATA_DIR $ZK_DATA_LOG_DIR $ZK_LOG_DIR /usr/share/zookeeper /tmp/zookeeper /usr/etc/ \
  67 + && chown -R "$ZK_USER:$ZK_USER" /opt/$ZK_DIST $ZK_DATA_DIR $ZK_LOG_DIR $ZK_DATA_LOG_DIR /tmp/zookeeper \
  68 + && ln -s /opt/zookeeper/conf/ /usr/etc/zookeeper \
  69 + && ln -s /opt/zookeeper/bin/* /usr/bin \
  70 + && ln -s /opt/zookeeper/$ZK_DIST.jar /usr/share/zookeeper/ \
  71 + && ln -s /opt/zookeeper/lib/* /usr/share/zookeeper
  1 +VERSION=1.2.4
  2 +PROJECT=thingsboard
  3 +APP=zk
  4 +
  5 +build:
  6 + docker build --pull -t ${PROJECT}/${APP}:${VERSION} .
  7 +
  8 +push: build
  9 + docker push ${PROJECT}/${APP}:${VERSION}
  1 +#!/usr/bin/env bash
  2 +#
  3 +# Copyright © 2016-2017 The Thingsboard Authors
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License");
  6 +# you may not use this file except in compliance with the License.
  7 +# You may obtain a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS,
  13 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +# See the License for the specific language governing permissions and
  15 +# limitations under the License.
  16 +#
  17 +
  18 +ZK_USER=${ZK_USER:-"zookeeper"}
  19 +ZK_LOG_LEVEL=${ZK_LOG_LEVEL:-"INFO"}
  20 +ZK_DATA_DIR=${ZK_DATA_DIR:-"/var/lib/zookeeper/data"}
  21 +ZK_DATA_LOG_DIR=${ZK_DATA_LOG_DIR:-"/var/lib/zookeeper/log"}
  22 +ZK_LOG_DIR=${ZK_LOG_DIR:-"var/log/zookeeper"}
  23 +ZK_CONF_DIR=${ZK_CONF_DIR:-"/opt/zookeeper/conf"}
  24 +ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
  25 +ZK_SERVER_PORT=${ZK_SERVER_PORT:-2888}
  26 +ZK_ELECTION_PORT=${ZK_ELECTION_PORT:-3888}
  27 +ZK_TICK_TIME=${ZK_TICK_TIME:-2000}
  28 +ZK_INIT_LIMIT=${ZK_INIT_LIMIT:-10}
  29 +ZK_SYNC_LIMIT=${ZK_SYNC_LIMIT:-5}
  30 +ZK_HEAP_SIZE=${ZK_HEAP_SIZE:-2G}
  31 +ZK_MAX_CLIENT_CNXNS=${ZK_MAX_CLIENT_CNXNS:-60}
  32 +ZK_MIN_SESSION_TIMEOUT=${ZK_MIN_SESSION_TIMEOUT:- $((ZK_TICK_TIME*2))}
  33 +ZK_MAX_SESSION_TIMEOUT=${ZK_MAX_SESSION_TIMEOUT:- $((ZK_TICK_TIME*20))}
  34 +ZK_SNAP_RETAIN_COUNT=${ZK_SNAP_RETAIN_COUNT:-3}
  35 +ZK_PURGE_INTERVAL=${ZK_PURGE_INTERVAL:-0}
  36 +ID_FILE="$ZK_DATA_DIR/myid"
  37 +ZK_CONFIG_FILE="$ZK_CONF_DIR/zoo.cfg"
  38 +LOGGER_PROPS_FILE="$ZK_CONF_DIR/log4j.properties"
  39 +JAVA_ENV_FILE="$ZK_CONF_DIR/java.env"
  40 +HOST=`hostname -s`
  41 +DOMAIN=`hostname -d`
  42 +
  43 +function print_servers() {
  44 + for (( i=1; i<=$ZK_REPLICAS; i++ ))
  45 + do
  46 + echo "server.$i=$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT"
  47 + done
  48 +}
  49 +
  50 +function validate_env() {
  51 + echo "Validating environment"
  52 + if [ -z $ZK_REPLICAS ]; then
  53 + echo "ZK_REPLICAS is a mandatory environment variable"
  54 + exit 1
  55 + fi
  56 +
  57 + if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then
  58 + NAME=${BASH_REMATCH[1]}
  59 + ORD=${BASH_REMATCH[2]}
  60 + else
  61 + echo "Failed to extract ordinal from hostname $HOST"
  62 + exit 1
  63 + fi
  64 + MY_ID=$((ORD+1))
  65 + echo "ZK_REPLICAS=$ZK_REPLICAS"
  66 + echo "MY_ID=$MY_ID"
  67 + echo "ZK_LOG_LEVEL=$ZK_LOG_LEVEL"
  68 + echo "ZK_DATA_DIR=$ZK_DATA_DIR"
  69 + echo "ZK_DATA_LOG_DIR=$ZK_DATA_LOG_DIR"
  70 + echo "ZK_LOG_DIR=$ZK_LOG_DIR"
  71 + echo "ZK_CLIENT_PORT=$ZK_CLIENT_PORT"
  72 + echo "ZK_SERVER_PORT=$ZK_SERVER_PORT"
  73 + echo "ZK_ELECTION_PORT=$ZK_ELECTION_PORT"
  74 + echo "ZK_TICK_TIME=$ZK_TICK_TIME"
  75 + echo "ZK_INIT_LIMIT=$ZK_INIT_LIMIT"
  76 + echo "ZK_SYNC_LIMIT=$ZK_SYNC_LIMIT"
  77 + echo "ZK_MAX_CLIENT_CNXNS=$ZK_MAX_CLIENT_CNXNS"
  78 + echo "ZK_MIN_SESSION_TIMEOUT=$ZK_MIN_SESSION_TIMEOUT"
  79 + echo "ZK_MAX_SESSION_TIMEOUT=$ZK_MAX_SESSION_TIMEOUT"
  80 + echo "ZK_HEAP_SIZE=$ZK_HEAP_SIZE"
  81 + echo "ZK_SNAP_RETAIN_COUNT=$ZK_SNAP_RETAIN_COUNT"
  82 + echo "ZK_PURGE_INTERVAL=$ZK_PURGE_INTERVAL"
  83 + echo "ENSEMBLE"
  84 + print_servers
  85 + echo "Environment validation successful"
  86 +}
  87 +
  88 +function create_config() {
  89 + rm -f $ZK_CONFIG_FILE
  90 + echo "Creating ZooKeeper configuration"
  91 + echo "#This file was autogenerated by zk DO NOT EDIT" >> $ZK_CONFIG_FILE
  92 + echo "clientPort=$ZK_CLIENT_PORT" >> $ZK_CONFIG_FILE
  93 + echo "dataDir=$ZK_DATA_DIR" >> $ZK_CONFIG_FILE
  94 + echo "dataLogDir=$ZK_DATA_LOG_DIR" >> $ZK_CONFIG_FILE
  95 + echo "tickTime=$ZK_TICK_TIME" >> $ZK_CONFIG_FILE
  96 + echo "initLimit=$ZK_INIT_LIMIT" >> $ZK_CONFIG_FILE
  97 + echo "syncLimit=$ZK_SYNC_LIMIT" >> $ZK_CONFIG_FILE
  98 + echo "maxClientCnxns=$ZK_MAX_CLIENT_CNXNS" >> $ZK_CONFIG_FILE
  99 + echo "minSessionTimeout=$ZK_MIN_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
  100 + echo "maxSessionTimeout=$ZK_MAX_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
  101 + echo "autopurge.snapRetainCount=$ZK_SNAP_RETAIN_COUNT" >> $ZK_CONFIG_FILE
  102 + echo "autopurge.purgeInteval=$ZK_PURGE_INTERVAL" >> $ZK_CONFIG_FILE
  103 +
  104 + if [ $ZK_REPLICAS -gt 1 ]; then
  105 + print_servers >> $ZK_CONFIG_FILE
  106 + fi
  107 + echo "Wrote ZooKeeper configuration file to $ZK_CONFIG_FILE"
  108 +}
  109 +
  110 +function create_data_dirs() {
  111 + echo "Creating ZooKeeper data directories and setting permissions"
  112 + if [ ! -d $ZK_DATA_DIR ]; then
  113 + mkdir -p $ZK_DATA_DIR
  114 + chown -R $ZK_USER:$ZK_USER $ZK_DATA_DIR
  115 + fi
  116 +
  117 + if [ ! -d $ZK_DATA_LOG_DIR ]; then
  118 + mkdir -p $ZK_DATA_LOG_DIR
  119 + chown -R $ZK_USER:$ZK_USER $ZK_DATA_LOG_DIR
  120 + fi
  121 +
  122 + if [ ! -d $ZK_LOG_DIR ]; then
  123 + mkdir -p $ZK_LOG_DIR
  124 + chown -R $ZK_USER:$ZK_USER $ZK_LOG_DIR
  125 + fi
  126 + if [ ! -f $ID_FILE ]; then
  127 + echo $MY_ID >> $ID_FILE
  128 + fi
  129 + echo "Created ZooKeeper data directories and set permissions in $ZK_DATA_DIR"
  130 +}
  131 +
  132 +function create_log_props () {
  133 + rm -f $LOGGER_PROPS_FILE
  134 + echo "Creating ZooKeeper log4j configuration"
  135 + echo "zookeeper.root.logger=CONSOLE" >> $LOGGER_PROPS_FILE
  136 + echo "zookeeper.console.threshold="$ZK_LOG_LEVEL >> $LOGGER_PROPS_FILE
  137 + echo "log4j.rootLogger=\${zookeeper.root.logger}" >> $LOGGER_PROPS_FILE
  138 + echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender" >> $LOGGER_PROPS_FILE
  139 + echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}" >> $LOGGER_PROPS_FILE
  140 + echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout" >> $LOGGER_PROPS_FILE
  141 + echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n" >> $LOGGER_PROPS_FILE
  142 + echo "Wrote log4j configuration to $LOGGER_PROPS_FILE"
  143 +}
  144 +
  145 +function create_java_env() {
  146 + rm -f $JAVA_ENV_FILE
  147 + echo "Creating JVM configuration file"
  148 + echo "ZOO_LOG_DIR=$ZK_LOG_DIR" >> $JAVA_ENV_FILE
  149 + echo "JVMFLAGS=\"-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE\"" >> $JAVA_ENV_FILE
  150 + echo "Wrote JVM configuration to $JAVA_ENV_FILE"
  151 +}
  152 +
  153 +validate_env && create_config && create_log_props && create_data_dirs && create_java_env
docker/zookeeper/zk-ok.sh renamed from docker/thingsboard/build_and_deploy.sh
1 -#!/bin/bash 1 +#!/usr/bin/env bash
2 # 2 #
3 # Copyright © 2016-2017 The Thingsboard Authors 3 # Copyright © 2016-2017 The Thingsboard Authors
4 # 4 #
@@ -15,12 +15,14 @@ @@ -15,12 +15,14 @@
15 # limitations under the License. 15 # limitations under the License.
16 # 16 #
17 17
  18 +# zk-ok.sh uses the ruok ZooKeeper four letter work to determine if the instance
  19 +# is health. The $? variable will be set to 0 if server responds that it is
  20 +# healthy, or 1 if the server fails to respond.
18 21
19 -cp ../../application/target/thingsboard.deb thingsboard.deb  
20 -  
21 -docker build -t thingsboard/application:1.2.3 -t thingsboard/application:latest .  
22 -  
23 -docker login  
24 -  
25 -docker push thingsboard/application:1.2.3  
26 -docker push thingsboard/application:latest  
  22 +ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
  23 +OK=$(echo ruok | nc 127.0.0.1 $ZK_CLIENT_PORT)
  24 +if [ "$OK" == "imok" ]; then
  25 + exit 0
  26 +else
  27 + exit 1
  28 +fi
  1 +#
  2 +# Copyright © 2016-2017 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +apiVersion: v1
  18 +kind: Service
  19 +metadata:
  20 + name: zk-headless
  21 + labels:
  22 + app: zk-headless
  23 +spec:
  24 + ports:
  25 + - port: 2888
  26 + name: server
  27 + - port: 3888
  28 + name: leader-election
  29 + clusterIP: None
  30 + selector:
  31 + app: zk
  32 +---
  33 +apiVersion: v1
  34 +kind: ConfigMap
  35 +metadata:
  36 + name: zk-config
  37 +data:
  38 + ensemble: "zk-0;zk-1;zk-2"
  39 + replicas: "3"
  40 + jvm.heap: "500m"
  41 + tick: "2000"
  42 + init: "10"
  43 + sync: "5"
  44 + client.cnxns: "60"
  45 + snap.retain: "3"
  46 + purge.interval: "1"
  47 + client.port: "2181"
  48 + server.port: "2888"
  49 + election.port: "3888"
  50 +---
  51 +apiVersion: policy/v1beta1
  52 +kind: PodDisruptionBudget
  53 +metadata:
  54 + name: zk-budget
  55 +spec:
  56 + selector:
  57 + matchLabels:
  58 + app: zk
  59 + minAvailable: 3
  60 +---
  61 +apiVersion: apps/v1beta1
  62 +kind: StatefulSet
  63 +metadata:
  64 + name: zk
  65 +spec:
  66 + serviceName: zk-headless
  67 + replicas: 3
  68 + template:
  69 + metadata:
  70 + labels:
  71 + app: zk
  72 + annotations:
  73 + pod.alpha.kubernetes.io/initialized: "true"
  74 + spec:
  75 + nodeSelector:
  76 + machinetype: other
  77 + affinity:
  78 + podAntiAffinity:
  79 + requiredDuringSchedulingIgnoredDuringExecution:
  80 + - labelSelector:
  81 + matchExpressions:
  82 + - key: "app"
  83 + operator: In
  84 + values:
  85 + - zk-headless
  86 + topologyKey: "kubernetes.io/hostname"
  87 + containers:
  88 + - name: zk
  89 + imagePullPolicy: Always
  90 + image: thingsboard/zk:1.2.4
  91 + ports:
  92 + - containerPort: 2181
  93 + name: client
  94 + - containerPort: 2888
  95 + name: server
  96 + - containerPort: 3888
  97 + name: leader-election
  98 + env:
  99 + - name : ZK_ENSEMBLE
  100 + valueFrom:
  101 + configMapKeyRef:
  102 + name: zk-config
  103 + key: ensemble
  104 + - name : ZK_REPLICAS
  105 + valueFrom:
  106 + configMapKeyRef:
  107 + name: zk-config
  108 + key: replicas
  109 + - name : ZK_HEAP_SIZE
  110 + valueFrom:
  111 + configMapKeyRef:
  112 + name: zk-config
  113 + key: jvm.heap
  114 + - name : ZK_TICK_TIME
  115 + valueFrom:
  116 + configMapKeyRef:
  117 + name: zk-config
  118 + key: tick
  119 + - name : ZK_INIT_LIMIT
  120 + valueFrom:
  121 + configMapKeyRef:
  122 + name: zk-config
  123 + key: init
  124 + - name : ZK_SYNC_LIMIT
  125 + valueFrom:
  126 + configMapKeyRef:
  127 + name: zk-config
  128 + key: tick
  129 + - name : ZK_MAX_CLIENT_CNXNS
  130 + valueFrom:
  131 + configMapKeyRef:
  132 + name: zk-config
  133 + key: client.cnxns
  134 + - name: ZK_SNAP_RETAIN_COUNT
  135 + valueFrom:
  136 + configMapKeyRef:
  137 + name: zk-config
  138 + key: snap.retain
  139 + - name: ZK_PURGE_INTERVAL
  140 + valueFrom:
  141 + configMapKeyRef:
  142 + name: zk-config
  143 + key: purge.interval
  144 + - name: ZK_CLIENT_PORT
  145 + valueFrom:
  146 + configMapKeyRef:
  147 + name: zk-config
  148 + key: client.port
  149 + - name: ZK_SERVER_PORT
  150 + valueFrom:
  151 + configMapKeyRef:
  152 + name: zk-config
  153 + key: server.port
  154 + - name: ZK_ELECTION_PORT
  155 + valueFrom:
  156 + configMapKeyRef:
  157 + name: zk-config
  158 + key: election.port
  159 + command:
  160 + - sh
  161 + - -c
  162 + - zk-gen-config.sh && zkServer.sh start-foreground
  163 + readinessProbe:
  164 + exec:
  165 + command:
  166 + - "zk-ok.sh"
  167 + initialDelaySeconds: 15
  168 + timeoutSeconds: 5
  169 + livenessProbe:
  170 + exec:
  171 + command:
  172 + - "zk-ok.sh"
  173 + initialDelaySeconds: 15
  174 + timeoutSeconds: 5
  175 + volumeMounts:
  176 + - name: zkdatadir
  177 + mountPath: /var/lib/zookeeper
  178 + securityContext:
  179 + runAsUser: 1000
  180 + fsGroup: 1000
  181 + volumeClaimTemplates:
  182 + - metadata:
  183 + name: zkdatadir
  184 + annotations:
  185 + volume.beta.kubernetes.io/storage-class: slow
  186 + spec:
  187 + accessModes: [ "ReadWriteOnce" ]
  188 + resources:
  189 + requests:
  190 + storage: 1Gi
@@ -41,7 +41,7 @@ import java.security.cert.X509Certificate; @@ -41,7 +41,7 @@ import java.security.cert.X509Certificate;
41 */ 41 */
42 @Slf4j 42 @Slf4j
43 @Component("MqttSslHandlerProvider") 43 @Component("MqttSslHandlerProvider")
44 -@ConditionalOnProperty(prefix = "mqtt.ssl", value = "key-store", havingValue = "", matchIfMissing = false) 44 +@ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
45 public class MqttSslHandlerProvider { 45 public class MqttSslHandlerProvider {
46 46
47 public static final String TLS = "TLS"; 47 public static final String TLS = "TLS";
@@ -25,6 +25,7 @@ function EntityRelationService($http, $q) { @@ -25,6 +25,7 @@ function EntityRelationService($http, $q) {
25 deleteRelation: deleteRelation, 25 deleteRelation: deleteRelation,
26 deleteRelations: deleteRelations, 26 deleteRelations: deleteRelations,
27 findByFrom: findByFrom, 27 findByFrom: findByFrom,
  28 + findInfoByFrom: findInfoByFrom,
28 findByFromAndType: findByFromAndType, 29 findByFromAndType: findByFromAndType,
29 findByTo: findByTo, 30 findByTo: findByTo,
30 findByToAndType: findByToAndType, 31 findByToAndType: findByToAndType,
@@ -84,6 +85,18 @@ function EntityRelationService($http, $q) { @@ -84,6 +85,18 @@ function EntityRelationService($http, $q) {
84 return deferred.promise; 85 return deferred.promise;
85 } 86 }
86 87
  88 + function findInfoByFrom(fromId, fromType) {
  89 + var deferred = $q.defer();
  90 + var url = '/api/relations/info?fromId=' + fromId;
  91 + url += '&fromType=' + fromType;
  92 + $http.get(url, null).then(function success(response) {
  93 + deferred.resolve(response.data);
  94 + }, function fail() {
  95 + deferred.reject();
  96 + });
  97 + return deferred.promise;
  98 + }
  99 +
87 function findByFromAndType(fromId, fromType, relationType) { 100 function findByFromAndType(fromId, fromType, relationType) {
88 var deferred = $q.defer(); 101 var deferred = $q.defer();
89 var url = '/api/relations?fromId=' + fromId; 102 var url = '/api/relations?fromId=' + fromId;
@@ -20,14 +20,13 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes]) @@ -20,14 +20,13 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
20 .name; 20 .name;
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -function EntityService($http, $q, $filter, $translate, userService, deviceService, 23 +function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
24 assetService, tenantService, customerService, 24 assetService, tenantService, customerService,
25 - ruleService, pluginService, entityRelationService, attributeService, types, utils) { 25 + ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) {
26 var service = { 26 var service = {
27 getEntity: getEntity, 27 getEntity: getEntity,
28 getEntities: getEntities, 28 getEntities: getEntities,
29 getEntitiesByNameFilter: getEntitiesByNameFilter, 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30 - entityName: entityName,  
31 processEntityAliases: processEntityAliases, 30 processEntityAliases: processEntityAliases,
32 getEntityKeys: getEntityKeys, 31 getEntityKeys: getEntityKeys,
33 checkEntityAlias: checkEntityAlias, 32 checkEntityAlias: checkEntityAlias,
@@ -63,6 +62,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic @@ -63,6 +62,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
63 case types.entityType.plugin: 62 case types.entityType.plugin:
64 promise = pluginService.getPlugin(entityId); 63 promise = pluginService.getPlugin(entityId);
65 break; 64 break;
  65 + case types.entityType.dashboard:
  66 + promise = dashboardService.getDashboardInfo(entityId);
  67 + break;
  68 + case types.entityType.user:
  69 + promise = userService.getUser(entityId);
  70 + break;
  71 + case types.entityType.alarm:
  72 + $log.error('Get Alarm Entity is not implemented!');
  73 + break;
66 } 74 }
67 return promise; 75 return promise;
68 } 76 }
@@ -134,6 +142,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic @@ -134,6 +142,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
134 case types.entityType.plugin: 142 case types.entityType.plugin:
135 promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds); 143 promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds);
136 break; 144 break;
  145 + case types.entityType.dashboard:
  146 + promise = getEntitiesByIdsPromise(dashboardService.getDashboardInfo, entityIds);
  147 + break;
  148 + case types.entityType.user:
  149 + promise = getEntitiesByIdsPromise(userService.getUser, entityIds);
  150 + break;
  151 + case types.entityType.alarm:
  152 + $log.error('Get Alarm Entity is not implemented!');
  153 + break;
137 } 154 }
138 return promise; 155 return promise;
139 } 156 }
@@ -141,34 +158,38 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic @@ -141,34 +158,38 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
141 function getEntities(entityType, entityIds, config) { 158 function getEntities(entityType, entityIds, config) {
142 var deferred = $q.defer(); 159 var deferred = $q.defer();
143 var promise = getEntitiesPromise(entityType, entityIds, config); 160 var promise = getEntitiesPromise(entityType, entityIds, config);
144 - promise.then(  
145 - function success(result) {  
146 - deferred.resolve(result);  
147 - },  
148 - function fail() {  
149 - deferred.reject();  
150 - }  
151 - ); 161 + if (promise) {
  162 + promise.then(
  163 + function success(result) {
  164 + deferred.resolve(result);
  165 + },
  166 + function fail() {
  167 + deferred.reject();
  168 + }
  169 + );
  170 + } else {
  171 + deferred.reject();
  172 + }
152 return deferred.promise; 173 return deferred.promise;
153 } 174 }
154 175
155 - function getEntitiesByPageLinkPromise(entityType, pageLink, config) { 176 + function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
156 var promise; 177 var promise;
157 var user = userService.getCurrentUser(); 178 var user = userService.getCurrentUser();
158 var customerId = user.customerId; 179 var customerId = user.customerId;
159 switch (entityType) { 180 switch (entityType) {
160 case types.entityType.device: 181 case types.entityType.device:
161 if (user.authority === 'CUSTOMER_USER') { 182 if (user.authority === 'CUSTOMER_USER') {
162 - promise = deviceService.getCustomerDevices(customerId, pageLink, false, config); 183 + promise = deviceService.getCustomerDevices(customerId, pageLink, false, config, subType);
163 } else { 184 } else {
164 - promise = deviceService.getTenantDevices(pageLink, false, config); 185 + promise = deviceService.getTenantDevices(pageLink, false, config, subType);
165 } 186 }
166 break; 187 break;
167 case types.entityType.asset: 188 case types.entityType.asset:
168 if (user.authority === 'CUSTOMER_USER') { 189 if (user.authority === 'CUSTOMER_USER') {
169 - promise = assetService.getCustomerAssets(customerId, pageLink, false, config); 190 + promise = assetService.getCustomerAssets(customerId, pageLink, false, config, subType);
170 } else { 191 } else {
171 - promise = assetService.getTenantAssets(pageLink, false, config); 192 + promise = assetService.getTenantAssets(pageLink, false, config, subType);
172 } 193 }
173 break; 194 break;
174 case types.entityType.tenant: 195 case types.entityType.tenant:
@@ -183,48 +204,48 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic @@ -183,48 +204,48 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
183 case types.entityType.plugin: 204 case types.entityType.plugin:
184 promise = pluginService.getAllPlugins(pageLink); 205 promise = pluginService.getAllPlugins(pageLink);
185 break; 206 break;
  207 + case types.entityType.dashboard:
  208 + if (user.authority === 'CUSTOMER_USER') {
  209 + promise = dashboardService.getCustomerDashboards(customerId, pageLink);
  210 + } else {
  211 + promise = dashboardService.getTenantDashboards(pageLink);
  212 + }
  213 + break;
  214 + case types.entityType.user:
  215 + $log.error('Get User Entities is not implemented!');
  216 + break;
  217 + case types.entityType.alarm:
  218 + $log.error('Get Alarm Entities is not implemented!');
  219 + break;
186 } 220 }
187 return promise; 221 return promise;
188 } 222 }
189 223
190 - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config) { 224 + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
191 var deferred = $q.defer(); 225 var deferred = $q.defer();
192 var pageLink = {limit: limit, textSearch: entityNameFilter}; 226 var pageLink = {limit: limit, textSearch: entityNameFilter};
193 - var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config);  
194 - promise.then(  
195 - function success(result) {  
196 - if (result.data && result.data.length > 0) {  
197 - deferred.resolve(result.data);  
198 - } else { 227 + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
  228 + if (promise) {
  229 + promise.then(
  230 + function success(result) {
  231 + if (result.data && result.data.length > 0) {
  232 + deferred.resolve(result.data);
  233 + } else {
  234 + deferred.resolve(null);
  235 + }
  236 + },
  237 + function fail() {
199 deferred.resolve(null); 238 deferred.resolve(null);
200 } 239 }
201 - },  
202 - function fail() {  
203 - deferred.resolve(null);  
204 - }  
205 - );  
206 - return deferred.promise;  
207 - }  
208 -  
209 - function entityName(entityType, entity) {  
210 - var name = '';  
211 - switch (entityType) {  
212 - case types.entityType.device:  
213 - case types.entityType.asset:  
214 - case types.entityType.rule:  
215 - case types.entityType.plugin:  
216 - name = entity.name;  
217 - break;  
218 - case types.entityType.tenant:  
219 - case types.entityType.customer:  
220 - name = entity.title;  
221 - break; 240 + );
  241 + } else {
  242 + deferred.resolve(null);
222 } 243 }
223 - return name; 244 + return deferred.promise;
224 } 245 }
225 246
226 function entityToEntityInfo(entityType, entity) { 247 function entityToEntityInfo(entityType, entity) {
227 - return { name: entityName(entityType, entity), entityType: entityType, id: entity.id.id }; 248 + return { name: entity.name, entityType: entityType, id: entity.id.id };
228 } 249 }
229 250
230 function entitiesToEntitiesInfo(entityType, entities) { 251 function entitiesToEntitiesInfo(entityType, entities) {
@@ -55,4 +55,10 @@ @@ -55,4 +55,10 @@
55 default-event-type="{{vm.types.eventType.alarm.value}}"> 55 default-event-type="{{vm.types.eventType.alarm.value}}">
56 </tb-event-table> 56 </tb-event-table>
57 </md-tab> 57 </md-tab>
  58 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
  59 + <tb-relation-table flex
  60 + entity-id="vm.grid.operatingItem().id.id"
  61 + entity-type="{{vm.types.entityType.asset}}">
  62 + </tb-relation-table>
  63 + </md-tab>
58 </tb-grid> 64 </tb-grid>
@@ -98,7 +98,10 @@ export default angular.module('thingsboard.types', []) @@ -98,7 +98,10 @@ export default angular.module('thingsboard.types', [])
98 rule: "RULE", 98 rule: "RULE",
99 plugin: "PLUGIN", 99 plugin: "PLUGIN",
100 tenant: "TENANT", 100 tenant: "TENANT",
101 - customer: "CUSTOMER" 101 + customer: "CUSTOMER",
  102 + user: "USER",
  103 + dashboard: "DASHBOARD",
  104 + alarm: "ALARM"
102 }, 105 },
103 entitySearchDirection: { 106 entitySearchDirection: {
104 from: "FROM", 107 from: "FROM",
@@ -108,7 +108,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -108,7 +108,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
108 guid: guid, 108 guid: guid,
109 isLocalUrl: isLocalUrl, 109 isLocalUrl: isLocalUrl,
110 validateDatasources: validateDatasources, 110 validateDatasources: validateDatasources,
111 - createKey: createKey 111 + createKey: createKey,
  112 + entityTypeName: entityTypeName
112 } 113 }
113 114
114 return service; 115 return service;
@@ -346,4 +347,27 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -346,4 +347,27 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
346 return dataKey; 347 return dataKey;
347 } 348 }
348 349
  350 + function entityTypeName (type) {
  351 + switch (type) {
  352 + case types.entityType.device:
  353 + return 'entity.type-device';
  354 + case types.entityType.asset:
  355 + return 'entity.type-asset';
  356 + case types.entityType.rule:
  357 + return 'entity.type-rule';
  358 + case types.entityType.plugin:
  359 + return 'entity.type-plugin';
  360 + case types.entityType.tenant:
  361 + return 'entity.type-tenant';
  362 + case types.entityType.customer:
  363 + return 'entity.type-customer';
  364 + case types.entityType.user:
  365 + return 'entity.type-user';
  366 + case types.entityType.dashboard:
  367 + return 'entity.type-dashboard';
  368 + case types.entityType.alarm:
  369 + return 'entity.type-alarm';
  370 + }
  371 + }
  372 +
349 } 373 }
@@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
34 </md-item-template> 34 </md-item-template>
35 <md-not-found> 35 <md-not-found>
36 <div class="tb-not-found"> 36 <div class="tb-not-found">
37 - <span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span> 37 + <span translate translate-values='{ entity: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
38 </div> 38 </div>
39 </md-not-found> 39 </md-not-found>
40 <div ng-messages="theForm.dashboard.$error"> 40 <div ng-messages="theForm.dashboard.$error">
@@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
34 </md-item-template> 34 </md-item-template>
35 <md-not-found> 35 <md-not-found>
36 <div class="tb-not-found"> 36 <div class="tb-not-found">
37 - <span translate translate-values='{ plugin: pluginSearchText }'>plugin.no-plugins-matching</span> 37 + <span translate translate-values='{ entity: pluginSearchText }'>plugin.no-plugins-matching</span>
38 </div> 38 </div>
39 </md-not-found> 39 </md-not-found>
40 </md-autocomplete> 40 </md-autocomplete>
@@ -109,7 +109,7 @@ export default function EntityStateController($scope, $location, $state, $stateP @@ -109,7 +109,7 @@ export default function EntityStateController($scope, $location, $state, $stateP
109 if (params && params.entityId && params.entityId.id && params.entityId.entityType) { 109 if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
110 entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then( 110 entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(
111 function success(entity) { 111 function success(entity) {
112 - var entityName = entityService.entityName(params.entityId.entityType, entity); 112 + var entityName = entity.name;
113 deferred.resolve(entityName); 113 deferred.resolve(entityName);
114 }, 114 },
115 function fail() { 115 function fail() {
@@ -128,9 +128,9 @@ @@ -128,9 +128,9 @@
128 <table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise"> 128 <table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
129 <thead md-head md-order="query.order" md-on-reorder="onReorder"> 129 <thead md-head md-order="query.order" md-on-reorder="onReorder">
130 <tr md-row> 130 <tr md-row>
131 - <th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th>  
132 - <th md-column md-order-by="key"><span>Key</span></th>  
133 - <th md-column>Value</th> 131 + <th md-column md-order-by="lastUpdateTs"><span translate>attribute.last-update-time</span></th>
  132 + <th md-column md-order-by="key"><span translate>attribute.key</span></th>
  133 + <th md-column><span translate>attribute.value</span></th>
134 </tr> 134 </tr>
135 </thead> 135 </thead>
136 <tbody md-body> 136 <tbody md-body>
@@ -110,7 +110,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc @@ -110,7 +110,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
110 entityAlias.changed = false; 110 entityAlias.changed = false;
111 } 111 }
112 if (!entityAlias.changed && entity && entityAlias.entityType) { 112 if (!entityAlias.changed && entity && entityAlias.entityType) {
113 - entityAlias.alias = entityService.entityName(entityAlias.entityType, entity); 113 + entityAlias.alias = entity.name;
114 } 114 }
115 } 115 }
116 } 116 }
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './entity-autocomplete.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import entityAutocompleteTemplate from './entity-autocomplete.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function EntityAutocomplete($compile, $templateCache, $q, $filter, entityService, types) {
  26 +
  27 + var linker = function (scope, element, attrs, ngModelCtrl) {
  28 + var template = $templateCache.get(entityAutocompleteTemplate);
  29 + element.html(template);
  30 +
  31 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  32 + scope.entity = null;
  33 + scope.entitySearchText = '';
  34 +
  35 + scope.fetchEntities = function(searchText) {
  36 + var deferred = $q.defer();
  37 + entityService.getEntitiesByNameFilter(scope.entityType, searchText, 50, null, scope.entitySubtype).then(function success(result) {
  38 + if (result) {
  39 + deferred.resolve(result);
  40 + } else {
  41 + deferred.resolve([]);
  42 + }
  43 + }, function fail() {
  44 + deferred.reject();
  45 + });
  46 + return deferred.promise;
  47 + }
  48 +
  49 + scope.entitySearchTextChanged = function() {
  50 + }
  51 +
  52 + scope.updateView = function () {
  53 + if (!scope.disabled) {
  54 + ngModelCtrl.$setViewValue(scope.entity ? scope.entity.id.id : null);
  55 + }
  56 + }
  57 +
  58 + ngModelCtrl.$render = function () {
  59 + if (ngModelCtrl.$viewValue) {
  60 + entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then(
  61 + function success(entity) {
  62 + scope.entity = entity;
  63 + },
  64 + function fail() {
  65 + scope.entity = null;
  66 + }
  67 + );
  68 + } else {
  69 + scope.entity = null;
  70 + }
  71 + }
  72 +
  73 + scope.$watch('entityType', function () {
  74 + load();
  75 + });
  76 +
  77 + scope.$watch('entitySubtype', function () {
  78 + if (scope.entity && scope.entity.type != scope.entitySubtype) {
  79 + scope.entity = null;
  80 + scope.updateView();
  81 + }
  82 + });
  83 +
  84 + scope.$watch('entity', function () {
  85 + scope.updateView();
  86 + });
  87 +
  88 + scope.$watch('disabled', function () {
  89 + scope.updateView();
  90 + });
  91 +
  92 +
  93 + function load() {
  94 + switch (scope.entityType) {
  95 + case types.entityType.asset:
  96 + scope.selectEntityText = 'asset.select-asset';
  97 + scope.entityText = 'asset.asset';
  98 + scope.noEntitiesMatchingText = 'asset.no-assets-matching';
  99 + scope.entityRequiredText = 'asset.asset-required'
  100 + break;
  101 + case types.entityType.device:
  102 + scope.selectEntityText = 'device.select-device';
  103 + scope.entityText = 'device.device';
  104 + scope.noEntitiesMatchingText = 'device.no-devices-matching';
  105 + scope.entityRequiredText = 'device.device-required'
  106 + break;
  107 + case types.entityType.rule:
  108 + scope.selectEntityText = 'rule.select-rule';
  109 + scope.entityText = 'rule.rule';
  110 + scope.noEntitiesMatchingText = 'rule.no-rules-matching';
  111 + scope.entityRequiredText = 'rule.rule-required'
  112 + break;
  113 + case types.entityType.plugin:
  114 + scope.selectEntityText = 'plugin.select-plugin';
  115 + scope.entityText = 'plugin.plugin';
  116 + scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
  117 + scope.entityRequiredText = 'plugin.plugin-required'
  118 + break;
  119 + case types.entityType.tenant:
  120 + scope.selectEntityText = 'tenant.select-tenant';
  121 + scope.entityText = 'tenant.tenant';
  122 + scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
  123 + scope.entityRequiredText = 'tenant.tenant-required'
  124 + break;
  125 + case types.entityType.customer:
  126 + scope.selectEntityText = 'customer.select-customer';
  127 + scope.entityText = 'customer.customer';
  128 + scope.noEntitiesMatchingText = 'customer.no-customers-matching';
  129 + scope.entityRequiredText = 'customer.customer-required'
  130 + break;
  131 + case types.entityType.user:
  132 + scope.selectEntityText = 'user.select-user';
  133 + scope.entityText = 'user.user';
  134 + scope.noEntitiesMatchingText = 'user.no-users-matching';
  135 + scope.entityRequiredText = 'user.user-required'
  136 + break;
  137 + case types.entityType.dashboard:
  138 + scope.selectEntityText = 'dashboard.select-dashboard';
  139 + scope.entityText = 'dashboard.dashboard';
  140 + scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
  141 + scope.entityRequiredText = 'dashboard.dashboard-required'
  142 + break;
  143 + case types.entityType.alarm:
  144 + scope.selectEntityText = 'alarm.select-alarm';
  145 + scope.entityText = 'alarm.alarm';
  146 + scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
  147 + scope.entityRequiredText = 'alarm.alarm-required'
  148 + break;
  149 + }
  150 + if (scope.entity && scope.entity.id.entityType != scope.entityType) {
  151 + scope.entity = null;
  152 + scope.updateView();
  153 + }
  154 + }
  155 +
  156 + $compile(element.contents())(scope);
  157 + }
  158 +
  159 + return {
  160 + restrict: "E",
  161 + require: "^ngModel",
  162 + link: linker,
  163 + scope: {
  164 + theForm: '=?',
  165 + tbRequired: '=?',
  166 + disabled:'=ngDisabled',
  167 + entityType: '=',
  168 + entitySubtype: '=?'
  169 + }
  170 + };
  171 +}
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +.tb-entity-autocomplete {
  17 + .tb-entity-item {
  18 + display: block;
  19 + height: 48px;
  20 + }
  21 + li {
  22 + height: auto !important;
  23 + white-space: normal !important;
  24 + }
  25 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-autocomplete ng-required="tbRequired"
  19 + ng-disabled="disabled"
  20 + md-no-cache="true"
  21 + md-input-name="entity"
  22 + ng-model="entity"
  23 + md-selected-item="entity"
  24 + md-search-text="entitySearchText"
  25 + md-search-text-change="entitySearchTextChanged()"
  26 + md-items="item in fetchEntities(entitySearchText)"
  27 + md-item-text="item.name"
  28 + md-min-length="0"
  29 + md-floating-label="{{ entityText | translate }}"
  30 + md-select-on-match="true"
  31 + md-menu-class="tb-entity-autocomplete">
  32 + <md-item-template>
  33 + <div class="tb-entity-item">
  34 + <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
  35 + </div>
  36 + </md-item-template>
  37 + <md-not-found>
  38 + <div class="tb-not-found">
  39 + <span translate translate-values='{ entity: entitySearchText }'>{{ noEntitiesMatchingText }}</span>
  40 + </div>
  41 + </md-not-found>
  42 + <div ng-messages="theForm.entity.$error">
  43 + <div translate ng-message="required">{{ entityRequiredText }}</div>
  44 + </div>
  45 +</md-autocomplete>
@@ -32,14 +32,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti @@ -32,14 +32,6 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
32 32
33 scope.ngModelCtrl = ngModelCtrl; 33 scope.ngModelCtrl = ngModelCtrl;
34 34
35 - scope.itemName = function(item) {  
36 - if (item) {  
37 - return entityService.entityName(scope.entityType, item);  
38 - } else {  
39 - return '';  
40 - }  
41 - }  
42 -  
43 scope.fetchEntities = function(searchText, limit) { 35 scope.fetchEntities = function(searchText, limit) {
44 var deferred = $q.defer(); 36 var deferred = $q.defer();
45 entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) { 37 entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {
@@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
33 md-min-length="0" 33 md-min-length="0"
34 placeholder="{{ 'entity.entity-list' | translate }}"> 34 placeholder="{{ 'entity.entity-list' | translate }}">
35 <md-item-template> 35 <md-item-template>
36 - <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{itemName(item)}}</span> 36 + <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
37 </md-item-template> 37 </md-item-template>
38 <md-not-found> 38 <md-not-found>
39 <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span> 39 <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import './entity-select.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import entitySelectTemplate from './entity-select.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function EntitySelect($compile, $templateCache) {
  26 +
  27 + var linker = function (scope, element, attrs, ngModelCtrl) {
  28 + var template = $templateCache.get(entitySelectTemplate);
  29 + element.html(template);
  30 +
  31 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  32 + scope.model = null;
  33 +
  34 + scope.updateView = function () {
  35 + if (!scope.disabled) {
  36 + var value = ngModelCtrl.$viewValue;
  37 + if (scope.model && scope.model.entityType && scope.model.entityId) {
  38 + if (!value) {
  39 + value = {};
  40 + }
  41 + value.entityType = scope.model.entityType;
  42 + value.id = scope.model.entityId;
  43 + ngModelCtrl.$setViewValue(value);
  44 + } else {
  45 + ngModelCtrl.$setViewValue(null);
  46 + }
  47 + }
  48 + }
  49 +
  50 + ngModelCtrl.$render = function () {
  51 + if (ngModelCtrl.$viewValue) {
  52 + var value = ngModelCtrl.$viewValue;
  53 + scope.model = {};
  54 + scope.model.entityType = value.entityType;
  55 + scope.model.entityId = value.id;
  56 + } else {
  57 + scope.model = null;
  58 + }
  59 + }
  60 +
  61 + scope.$watch('model.entityType', function () {
  62 + scope.updateView();
  63 + });
  64 +
  65 + scope.$watch('model.entityId', function () {
  66 + scope.updateView();
  67 + });
  68 +
  69 + scope.$watch('disabled', function () {
  70 + scope.updateView();
  71 + });
  72 +
  73 + $compile(element.contents())(scope);
  74 + }
  75 +
  76 + return {
  77 + restrict: "E",
  78 + require: "^ngModel",
  79 + link: linker,
  80 + scope: {
  81 + theForm: '=?',
  82 + tbRequired: '=?',
  83 + disabled:'=ngDisabled'
  84 + }
  85 + };
  86 +}
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +.tb-entity-select {
  18 +
  19 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div layout='row' class="tb-entity-select">
  19 + <tb-entity-type-select style="min-width: 100px;"
  20 + ng-model="model.entityType">
  21 + </tb-entity-type-select>
  22 + <tb-entity-autocomplete flex
  23 + the-form="theForm"
  24 + ng-disabled="disabled"
  25 + tb-required="tbRequired"
  26 + entity-type="model.entityType"
  27 + ng-model="model.entityId">
  28 + </tb-entity-autocomplete>
  29 +</div>
@@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html'; @@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
23 /* eslint-enable import/no-unresolved, import/default */ 23 /* eslint-enable import/no-unresolved, import/default */
24 24
25 /*@ngInject*/ 25 /*@ngInject*/
26 -export default function EntityTypeSelect($compile, $templateCache, userService, types) { 26 +export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
27 27
28 var linker = function (scope, element, attrs, ngModelCtrl) { 28 var linker = function (scope, element, attrs, ngModelCtrl) {
29 var template = $templateCache.get(entityTypeSelectTemplate); 29 var template = $templateCache.get(entityTypeSelectTemplate);
@@ -51,10 +51,12 @@ export default function EntityTypeSelect($compile, $templateCache, userService, @@ -51,10 +51,12 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
51 scope.entityTypes.customer = types.entityType.customer; 51 scope.entityTypes.customer = types.entityType.customer;
52 scope.entityTypes.rule = types.entityType.rule; 52 scope.entityTypes.rule = types.entityType.rule;
53 scope.entityTypes.plugin = types.entityType.plugin; 53 scope.entityTypes.plugin = types.entityType.plugin;
  54 + scope.entityTypes.dashboard = types.entityType.dashboard;
54 break; 55 break;
55 case 'CUSTOMER_USER': 56 case 'CUSTOMER_USER':
56 scope.entityTypes.device = types.entityType.device; 57 scope.entityTypes.device = types.entityType.device;
57 scope.entityTypes.asset = types.entityType.asset; 58 scope.entityTypes.asset = types.entityType.asset;
  59 + scope.entityTypes.dashboard = types.entityType.dashboard;
58 break; 60 break;
59 } 61 }
60 62
@@ -67,20 +69,7 @@ export default function EntityTypeSelect($compile, $templateCache, userService, @@ -67,20 +69,7 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
67 } 69 }
68 70
69 scope.typeName = function(type) { 71 scope.typeName = function(type) {
70 - switch (type) {  
71 - case types.entityType.device:  
72 - return 'entity.type-device';  
73 - case types.entityType.asset:  
74 - return 'entity.type-asset';  
75 - case types.entityType.rule:  
76 - return 'entity.type-rule';  
77 - case types.entityType.plugin:  
78 - return 'entity.type-plugin';  
79 - case types.entityType.tenant:  
80 - return 'entity.type-tenant';  
81 - case types.entityType.customer:  
82 - return 'entity.type-customer';  
83 - } 72 + return utils.entityTypeName(type);
84 } 73 }
85 74
86 scope.updateValidity = function () { 75 scope.updateValidity = function () {
@@ -18,12 +18,15 @@ import EntityAliasesController from './entity-aliases.controller'; @@ -18,12 +18,15 @@ import EntityAliasesController from './entity-aliases.controller';
18 import EntityTypeSelectDirective from './entity-type-select.directive'; 18 import EntityTypeSelectDirective from './entity-type-select.directive';
19 import EntitySubtypeSelectDirective from './entity-subtype-select.directive'; 19 import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
20 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive'; 20 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
  21 +import EntityAutocompleteDirective from './entity-autocomplete.directive';
  22 +import EntitySelectDirective from './entity-select.directive';
21 import EntityFilterDirective from './entity-filter.directive'; 23 import EntityFilterDirective from './entity-filter.directive';
22 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller'; 24 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
23 import AliasesEntitySelectDirective from './aliases-entity-select.directive'; 25 import AliasesEntitySelectDirective from './aliases-entity-select.directive';
24 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller'; 26 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
25 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller'; 27 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
26 import AttributeTableDirective from './attribute/attribute-table.directive'; 28 import AttributeTableDirective from './attribute/attribute-table.directive';
  29 +import RelationTableDirective from './relation/relation-table.directive';
27 30
28 export default angular.module('thingsboard.entity', []) 31 export default angular.module('thingsboard.entity', [])
29 .controller('EntityAliasesController', EntityAliasesController) 32 .controller('EntityAliasesController', EntityAliasesController)
@@ -33,7 +36,10 @@ export default angular.module('thingsboard.entity', []) @@ -33,7 +36,10 @@ export default angular.module('thingsboard.entity', [])
33 .directive('tbEntityTypeSelect', EntityTypeSelectDirective) 36 .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
34 .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective) 37 .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
35 .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective) 38 .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
  39 + .directive('tbEntityAutocomplete', EntityAutocompleteDirective)
  40 + .directive('tbEntitySelect', EntitySelectDirective)
36 .directive('tbEntityFilter', EntityFilterDirective) 41 .directive('tbEntityFilter', EntityFilterDirective)
37 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective) 42 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
38 .directive('tbAttributeTable', AttributeTableDirective) 43 .directive('tbAttributeTable', AttributeTableDirective)
  44 + .directive('tbRelationTable', RelationTableDirective)
39 .name; 45 .name;
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/*@ngInject*/
  17 +export default function AddRelationDialogController($scope, $mdDialog, types, entityRelationService, from) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.types = types;
  22 +
  23 + vm.relation = {};
  24 + vm.relation.from = from;
  25 + vm.relation.type = types.entityRelationType.contains;
  26 +
  27 + vm.add = add;
  28 + vm.cancel = cancel;
  29 +
  30 + function cancel() {
  31 + $mdDialog.cancel();
  32 + }
  33 +
  34 + function add() {
  35 + $scope.theForm.$setPristine();
  36 + entityRelationService.saveRelation(vm.relation).then(
  37 + function success() {
  38 + $mdDialog.hide();
  39 + }
  40 + );
  41 + }
  42 +
  43 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'relation.add' | translate }}" style="min-width: 400px;">
  19 + <form name="theForm" ng-submit="vm.add()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>relation.add</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <md-content class="md-padding" layout="column">
  34 + <fieldset ng-disabled="loading">
  35 + <md-input-container class="md-block">
  36 + <label translate>relation.relation-type</label>
  37 + <md-select required ng-model="vm.relation.type" ng-disabled="loading">
  38 + <md-option ng-repeat="type in vm.types.entityRelationType" ng-value="type">
  39 + <span>{{('relation.relation-types.' + type) | translate}}</span>
  40 + </md-option>
  41 + </md-select>
  42 + </md-input-container>
  43 + <span class="tb-small">{{'entity.entity' | translate }}</span>
  44 + <tb-entity-select flex
  45 + the-form="theForm"
  46 + tb-required="true"
  47 + ng-model="vm.relation.to">
  48 + </tb-entity-select>
  49 + </fieldset>
  50 + </md-content>
  51 + </div>
  52 + </md-dialog-content>
  53 + <md-dialog-actions layout="row">
  54 + <span flex></span>
  55 + <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
  56 + class="md-raised md-primary">
  57 + {{ 'action.add' | translate }}
  58 + </md-button>
  59 + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
  60 + translate }}
  61 + </md-button>
  62 + </md-dialog-actions>
  63 + </form>
  64 +</md-dialog>
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import 'angular-material-data-table/dist/md-data-table.min.css';
  17 +import './relation-table.scss';
  18 +
  19 +/* eslint-disable import/no-unresolved, import/default */
  20 +
  21 +import relationTableTemplate from './relation-table.tpl.html';
  22 +import addRelationTemplate from './add-relation-dialog.tpl.html';
  23 +
  24 +/* eslint-enable import/no-unresolved, import/default */
  25 +
  26 +import AddRelationController from './add-relation-dialog.controller';
  27 +
  28 +/*@ngInject*/
  29 +export default function RelationTable() {
  30 + return {
  31 + restrict: "E",
  32 + scope: true,
  33 + bindToController: {
  34 + entityId: '=',
  35 + entityType: '@'
  36 + },
  37 + controller: RelationTableController,
  38 + controllerAs: 'vm',
  39 + templateUrl: relationTableTemplate
  40 + };
  41 +}
  42 +
  43 +/*@ngInject*/
  44 +function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, utils, types, entityRelationService) {
  45 +
  46 + let vm = this;
  47 +
  48 + vm.relations = [];
  49 + vm.relationsCount = 0;
  50 + vm.allRelations = [];
  51 + vm.selectedRelations = [];
  52 +
  53 + vm.query = {
  54 + order: 'typeName',
  55 + limit: 5,
  56 + page: 1,
  57 + search: null
  58 + };
  59 +
  60 + vm.enterFilterMode = enterFilterMode;
  61 + vm.exitFilterMode = exitFilterMode;
  62 + vm.onReorder = onReorder;
  63 + vm.onPaginate = onPaginate;
  64 + vm.addRelation = addRelation;
  65 + vm.editRelation = editRelation;
  66 + vm.deleteRelation = deleteRelation;
  67 + vm.deleteRelations = deleteRelations;
  68 + vm.reloadRelations = reloadRelations;
  69 + vm.updateRelations = updateRelations;
  70 +
  71 +
  72 + $scope.$watch("vm.entityId", function(newVal, prevVal) {
  73 + if (newVal && !angular.equals(newVal, prevVal)) {
  74 + reloadRelations();
  75 + }
  76 + });
  77 +
  78 + $scope.$watch("vm.query.search", function(newVal, prevVal) {
  79 + if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
  80 + updateRelations();
  81 + }
  82 + });
  83 +
  84 + function enterFilterMode () {
  85 + vm.query.search = '';
  86 + }
  87 +
  88 + function exitFilterMode () {
  89 + vm.query.search = null;
  90 + updateRelations();
  91 + }
  92 +
  93 + function onReorder () {
  94 + updateRelations();
  95 + }
  96 +
  97 + function onPaginate () {
  98 + updateRelations();
  99 + }
  100 +
  101 + function addRelation($event) {
  102 + if ($event) {
  103 + $event.stopPropagation();
  104 + }
  105 + var from = {
  106 + id: vm.entityId,
  107 + entityType: vm.entityType
  108 + };
  109 + $mdDialog.show({
  110 + controller: AddRelationController,
  111 + controllerAs: 'vm',
  112 + templateUrl: addRelationTemplate,
  113 + parent: angular.element($document[0].body),
  114 + locals: { from: from },
  115 + fullscreen: true,
  116 + targetEvent: $event
  117 + }).then(function () {
  118 + reloadRelations();
  119 + }, function () {
  120 + });
  121 + }
  122 +
  123 + function editRelation($event, /*relation*/) {
  124 + if ($event) {
  125 + $event.stopPropagation();
  126 + }
  127 + //TODO:
  128 + }
  129 +
  130 + function deleteRelation($event, /*relation*/) {
  131 + if ($event) {
  132 + $event.stopPropagation();
  133 + }
  134 + //TODO:
  135 + }
  136 +
  137 + function deleteRelations($event) {
  138 + if ($event) {
  139 + $event.stopPropagation();
  140 + }
  141 + //TODO:
  142 + }
  143 +
  144 + function reloadRelations () {
  145 + vm.allRelations.length = 0;
  146 + vm.relations.length = 0;
  147 + vm.relationsPromise = entityRelationService.findInfoByFrom(vm.entityId, vm.entityType);
  148 + vm.relationsPromise.then(
  149 + function success(allRelations) {
  150 + allRelations.forEach(function(relation) {
  151 + relation.typeName = $translate.instant('relation.relation-type.' + relation.type);
  152 + relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
  153 + });
  154 + vm.allRelations = allRelations;
  155 + vm.selectedRelations = [];
  156 + vm.updateRelations();
  157 + vm.relationsPromise = null;
  158 + },
  159 + function fail() {
  160 + vm.allRelations = [];
  161 + vm.selectedRelations = [];
  162 + vm.updateRelations();
  163 + vm.relationsPromise = null;
  164 + }
  165 + )
  166 + }
  167 +
  168 + function updateRelations () {
  169 + vm.selectedRelations = [];
  170 + var result = $filter('orderBy')(vm.allRelations, vm.query.order);
  171 + if (vm.query.search != null) {
  172 + result = $filter('filter')(result, {$: vm.query.search});
  173 + }
  174 + vm.relationsCount = result.length;
  175 + var startIndex = vm.query.limit * (vm.query.page - 1);
  176 + vm.relations = result.slice(startIndex, startIndex + vm.query.limit);
  177 + }
  178 +
  179 +}
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +@import '../../../scss/constants';
  17 +
  18 +$md-light: rgba(255, 255, 255, 100%);
  19 +
  20 +.tb-relation-table {
  21 + md-toolbar.md-table-toolbar.alternate {
  22 + .md-toolbar-tools {
  23 + md-icon {
  24 + color: $md-light;
  25 + }
  26 + }
  27 + }
  28 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-content flex class="md-padding tb-absolute-fill tb-relation-table tb-data-table" layout="column">
  19 + <div layout="column" class="md-whiteframe-z1">
  20 + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
  21 + && vm.query.search === null">
  22 + <div class="md-toolbar-tools">
  23 + <span translate>relation.entity-relations</span>
  24 + <span flex></span>
  25 + <md-button class="md-icon-button" ng-click="vm.addRelation($event)">
  26 + <md-icon>add</md-icon>
  27 + <md-tooltip md-direction="top">
  28 + {{ 'action.add' | translate }}
  29 + </md-tooltip>
  30 + </md-button>
  31 + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
  32 + <md-icon>search</md-icon>
  33 + <md-tooltip md-direction="top">
  34 + {{ 'action.search' | translate }}
  35 + </md-tooltip>
  36 + </md-button>
  37 + <md-button class="md-icon-button" ng-click="vm.reloadRelations()">
  38 + <md-icon>refresh</md-icon>
  39 + <md-tooltip md-direction="top">
  40 + {{ 'action.refresh' | translate }}
  41 + </md-tooltip>
  42 + </md-button>
  43 + </div>
  44 + </md-toolbar>
  45 + <md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
  46 + && vm.query.search != null">
  47 + <div class="md-toolbar-tools">
  48 + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
  49 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
  50 + <md-tooltip md-direction="top">
  51 + {{ 'action.search' | translate }}
  52 + </md-tooltip>
  53 + </md-button>
  54 + <md-input-container flex>
  55 + <label>&nbsp;</label>
  56 + <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
  57 + </md-input-container>
  58 + <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
  59 + <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
  60 + <md-tooltip md-direction="top">
  61 + {{ 'action.close' | translate }}
  62 + </md-tooltip>
  63 + </md-button>
  64 + </div>
  65 + </md-toolbar>
  66 + <md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedRelations.length">
  67 + <div class="md-toolbar-tools">
  68 + <span translate
  69 + translate-values="{count: selectedRelations.length}"
  70 + translate-interpolation="messageformat">relation.selected-relations</span>
  71 + <span flex></span>
  72 + <md-button class="md-icon-button" ng-click="vm.deleteRelations($event)">
  73 + <md-icon>delete</md-icon>
  74 + <md-tooltip md-direction="top">
  75 + {{ 'action.delete' | translate }}
  76 + </md-tooltip>
  77 + </md-button>
  78 + </div>
  79 + </md-toolbar>
  80 + <md-table-container>
  81 + <table md-table md-row-select multiple="" ng-model="vm.selectedRelations" md-progress="vm.relationsDeferred.promise">
  82 + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
  83 + <tr md-row>
  84 + <th md-column md-order-by="typeName"><span translate>relation.type</span></th>
  85 + <th md-column md-order-by="toEntityTypeName"><span translate>relation.to-entity-type</span></th>
  86 + <th md-column md-order-by="toName"><span translate>relation.to-entity-name</span></th>
  87 + <th md-column><span>&nbsp</span></th>
  88 + </tr>
  89 + </thead>
  90 + <tbody md-body>
  91 + <tr md-row md-select="relation" md-select-id="relation" md-auto-select ng-repeat="relation in vm.relations">
  92 + <td md-cell>{{ relation.typeName }}</td>
  93 + <td md-cell>{{ relation.toEntityTypeName }}</td>
  94 + <td md-cell>{{ relation.toName }}</td>
  95 + <td md-cell class="tb-action-cell">
  96 + <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
  97 + ng-click="vm.editRelation($event, relation)">
  98 + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
  99 + <md-tooltip md-direction="top">
  100 + {{ 'relation.edit' | translate }}
  101 + </md-tooltip>
  102 + </md-button>
  103 + <md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteRelation($event, relation)">
  104 + <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon>
  105 + <md-tooltip md-direction="top">
  106 + {{ 'relation.delete' | translate }}
  107 + </md-tooltip>
  108 + </md-button>
  109 + </td>
  110 + </tr>
  111 + </tbody>
  112 + </table>
  113 + </md-table-container>
  114 + <md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]"
  115 + md-page="vm.query.page" md-total="{{vm.relationsCount}}"
  116 + md-on-paginate="onPaginate" md-page-select>
  117 + </md-table-pagination>
  118 + </div>
  119 +</md-content>
@@ -235,7 +235,7 @@ @@ -235,7 +235,7 @@
235 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", 235 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
236 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", 236 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
237 "select-dashboard": "Seleccionar panel", 237 "select-dashboard": "Seleccionar panel",
238 - "no-dashboards-matching": "Panel '{{dashboard}}' no encontrado.", 238 + "no-dashboards-matching": "Panel '{{entity}}' no encontrado.",
239 "dashboard-required": "Panel requerido.", 239 "dashboard-required": "Panel requerido.",
240 "select-existing": "Seleccionar paneles existentes", 240 "select-existing": "Seleccionar paneles existentes",
241 "create-new": "Crear nuevo panel", 241 "create-new": "Crear nuevo panel",
@@ -330,7 +330,7 @@ @@ -330,7 +330,7 @@
330 "create-new-key": "Crear nueva clave!", 330 "create-new-key": "Crear nueva clave!",
331 "duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.", 331 "duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",
332 "configure-alias": "Configurar alias '{{alias}}'", 332 "configure-alias": "Configurar alias '{{alias}}'",
333 - "no-devices-matching": "No se encontró dispositivo '{{device}}'", 333 + "no-devices-matching": "No se encontró dispositivo '{{entity}}'",
334 "alias": "Alias", 334 "alias": "Alias",
335 "alias-required": "Alias de dispositivo requerido.", 335 "alias-required": "Alias de dispositivo requerido.",
336 "remove-alias": "Eliminar alias", 336 "remove-alias": "Eliminar alias",
@@ -529,7 +529,7 @@ @@ -529,7 +529,7 @@
529 "system": "Sistema", 529 "system": "Sistema",
530 "select-plugin": "plugin", 530 "select-plugin": "plugin",
531 "plugin": "Plugin", 531 "plugin": "Plugin",
532 - "no-plugins-matching": "No se encontraron plugins: '{{plugin}}'", 532 + "no-plugins-matching": "No se encontraron plugins: '{{entity}}'",
533 "plugin-required": "Plugin requerido.", 533 "plugin-required": "Plugin requerido.",
534 "plugin-require-match": "Por favor, elija un plugin existente.", 534 "plugin-require-match": "Por favor, elija un plugin existente.",
535 "events": "Eventos", 535 "events": "Eventos",
@@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) { @@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) {
218 "unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?", 218 "unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?",
219 "unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", 219 "unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.",
220 "select-dashboard": "대시보드 선택", 220 "select-dashboard": "대시보드 선택",
221 - "no-dashboards-matching": "'{{dashboard}}'와 일치하는 대시보드가 없습니다.", 221 + "no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.",
222 "dashboard-required": "대시보드를 입력하세요.", 222 "dashboard-required": "대시보드를 입력하세요.",
223 "select-existing": "기존 대시보드 선택", 223 "select-existing": "기존 대시보드 선택",
224 "create-new": "대시보드 생성", 224 "create-new": "대시보드 생성",
@@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) { @@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) {
305 "create-new-key": "새로 만들기!", 305 "create-new-key": "새로 만들기!",
306 "duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.<br> 디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.", 306 "duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.<br> 디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.",
307 "configure-alias": "'{{alias}}' 앨리어스 구성", 307 "configure-alias": "'{{alias}}' 앨리어스 구성",
308 - "no-devices-matching": "'{{device}}'와 일치하는 디바이스를 찾을 수 없습니다.", 308 + "no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.",
309 "alias": "앨리어스", 309 "alias": "앨리어스",
310 "alias-required": "디바이스 앨리어스를 입력하세요.", 310 "alias-required": "디바이스 앨리어스를 입력하세요.",
311 "remove-alias": "디바이스 앨리어스 삭제", 311 "remove-alias": "디바이스 앨리어스 삭제",
@@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) { @@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) {
496 "system": "시스템", 496 "system": "시스템",
497 "select-plugin": "플러그인 선택", 497 "select-plugin": "플러그인 선택",
498 "plugin": "플러그인", 498 "plugin": "플러그인",
499 - "no-plugins-matching": "'{{plugin}}'과 일치하는 플러그인을 찾을 수 없습니다.", 499 + "no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.",
500 "plugin-required": "플러그인을 입력하세요.", 500 "plugin-required": "플러그인을 입력하세요.",
501 "plugin-require-match": "기존의 플러그인을 선택해주세요.", 501 "plugin-require-match": "기존의 플러그인을 선택해주세요.",
502 "events": "이벤트", 502 "events": "이벤트",
@@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) { @@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) {
235 "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard", 235 "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard",
236 "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard", 236 "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard",
237 "select-dashboard": "Выберите дашборд", 237 "select-dashboard": "Выберите дашборд",
238 - "no-dashboards-matching": "Дашборд '{{dashboard}}' не найден.", 238 + "no-dashboards-matching": "Дашборд '{{entity}}' не найден.",
239 "dashboard-required": "Дашборд обязателен.", 239 "dashboard-required": "Дашборд обязателен.",
240 "select-existing": "Выберите существующий дашборд", 240 "select-existing": "Выберите существующий дашборд",
241 "create-new": "Создать новый дашборд", 241 "create-new": "Создать новый дашборд",
@@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) { @@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) {
330 "create-new-key": "Создать новый!", 330 "create-new-key": "Создать новый!",
331 "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.", 331 "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.",
332 "configure-alias": "Конфигурировать '{{alias}}' псевдоним", 332 "configure-alias": "Конфигурировать '{{alias}}' псевдоним",
333 - "no-devices-matching": "Устройство '{{device}}' не найдено.", 333 + "no-devices-matching": "Устройство '{{entity}}' не найдено.",
334 "alias": "Псевдоним", 334 "alias": "Псевдоним",
335 "alias-required": "Псевдоним устройства обязателен.", 335 "alias-required": "Псевдоним устройства обязателен.",
336 "remove-alias": "Удалить псевдоним устройства", 336 "remove-alias": "Удалить псевдоним устройства",
@@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) { @@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) {
529 "system": "Системный", 529 "system": "Системный",
530 "select-plugin": "Выберите плагин", 530 "select-plugin": "Выберите плагин",
531 "plugin": "Плагин", 531 "plugin": "Плагин",
532 - "no-plugins-matching": "Плагин '{{plugin}}' не найден.", 532 + "no-plugins-matching": "Плагин '{{entity}}' не найден.",
533 "plugin-required": "Плагин обязателен.", 533 "plugin-required": "Плагин обязателен.",
534 "plugin-require-match": "Пожалуйста, выберите существующий плагин.", 534 "plugin-require-match": "Пожалуйста, выберите существующий плагин.",
535 "events": "События", 535 "events": "События",
@@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) { @@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) {
235 "socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持", 235 "socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
236 "socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持", 236 "socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
237 "select-dashboard" : "选择仪表板", 237 "select-dashboard" : "选择仪表板",
238 - "no-dashboards-matching" : "找不到符合 '{{dashboard}}' 的仪表板。", 238 + "no-dashboards-matching" : "找不到符合 '{{entity}}' 的仪表板。",
239 "dashboard-required" : "仪表板是必需的。", 239 "dashboard-required" : "仪表板是必需的。",
240 "select-existing" : "选择现有仪表板", 240 "select-existing" : "选择现有仪表板",
241 "create-new" : "创建新的仪表板", 241 "create-new" : "创建新的仪表板",
@@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) { @@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) {
330 "create-new-key": "创建一个新的!", 330 "create-new-key": "创建一个新的!",
331 "duplicate-alias-error" : "找到重复别名 '{{alias}}'。 <br> 设备别名必须是唯一的。", 331 "duplicate-alias-error" : "找到重复别名 '{{alias}}'。 <br> 设备别名必须是唯一的。",
332 "configure-alias" : "配置 '{{alias}}' 别名", 332 "configure-alias" : "配置 '{{alias}}' 别名",
333 - "no-devices-matching" : "找不到与 '{{device}}' 匹配的设备。", 333 + "no-devices-matching" : "找不到与 '{{entity}}' 匹配的设备。",
334 "alias" : "别名", 334 "alias" : "别名",
335 "alias-required" : "需要设备别名。", 335 "alias-required" : "需要设备别名。",
336 "remove-alias": "删除设备别名", 336 "remove-alias": "删除设备别名",
@@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) { @@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) {
529 "system" : "系统", 529 "system" : "系统",
530 "select-plugin" : "选择插件", 530 "select-plugin" : "选择插件",
531 "plugin" : "插件", 531 "plugin" : "插件",
532 - "no-plugins-matching" : "没有找到匹配'{{plugin}}'的插件。", 532 + "no-plugins-matching" : "没有找到匹配'{{entity}}'的插件。",
533 "plugin-required" : "插件是必需的。", 533 "plugin-required" : "插件是必需的。",
534 "plugin-require-match" : "请选择一个现有的插件。", 534 "plugin-require-match" : "请选择一个现有的插件。",
535 "events" : "事件", 535 "events" : "事件",
@@ -106,6 +106,12 @@ export default angular.module('thingsboard.locale', []) @@ -106,6 +106,12 @@ export default angular.module('thingsboard.locale', [])
106 "enable-tls": "Enable TLS", 106 "enable-tls": "Enable TLS",
107 "send-test-mail": "Send test mail" 107 "send-test-mail": "Send test mail"
108 }, 108 },
  109 + "alarm": {
  110 + "alarm": "Alarm",
  111 + "select-alarm": "Select alarm",
  112 + "no-alarms-matching": "No alarms matching '{{entity}}' were found.",
  113 + "alarm-required": "Alarm is required"
  114 + },
109 "asset": { 115 "asset": {
110 "asset": "Asset", 116 "asset": "Asset",
111 "assets": "Assets", 117 "assets": "Assets",
@@ -157,7 +163,10 @@ export default angular.module('thingsboard.locale', []) @@ -157,7 +163,10 @@ export default angular.module('thingsboard.locale', [])
157 "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?", 163 "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
158 "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", 164 "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
159 "copyId": "Copy asset Id", 165 "copyId": "Copy asset Id",
160 - "idCopiedMessage": "Asset Id has been copied to clipboard" 166 + "idCopiedMessage": "Asset Id has been copied to clipboard",
  167 + "select-asset": "Select asset",
  168 + "no-assets-matching": "No assets matching '{{entity}}' were found.",
  169 + "asset-required": "Asset is required"
161 }, 170 },
162 "attribute": { 171 "attribute": {
163 "attributes": "Attributes", 172 "attributes": "Attributes",
@@ -169,6 +178,7 @@ export default angular.module('thingsboard.locale', []) @@ -169,6 +178,7 @@ export default angular.module('thingsboard.locale', [])
169 "scope-shared": "Shared attributes", 178 "scope-shared": "Shared attributes",
170 "add": "Add attribute", 179 "add": "Add attribute",
171 "key": "Key", 180 "key": "Key",
  181 + "last-update-time": "Last update time",
172 "key-required": "Attribute key is required.", 182 "key-required": "Attribute key is required.",
173 "value": "Value", 183 "value": "Value",
174 "value-required": "Attribute value is required.", 184 "value-required": "Attribute value is required.",
@@ -210,6 +220,7 @@ export default angular.module('thingsboard.locale', []) @@ -210,6 +220,7 @@ export default angular.module('thingsboard.locale', [])
210 "enter-search": "Enter search" 220 "enter-search": "Enter search"
211 }, 221 },
212 "customer": { 222 "customer": {
  223 + "customer": "Customer",
213 "customers": "Customers", 224 "customers": "Customers",
214 "management": "Customer management", 225 "management": "Customer management",
215 "dashboard": "Customer Dashboard", 226 "dashboard": "Customer Dashboard",
@@ -246,7 +257,10 @@ export default angular.module('thingsboard.locale', []) @@ -246,7 +257,10 @@ export default angular.module('thingsboard.locale', [])
246 "details": "Details", 257 "details": "Details",
247 "events": "Events", 258 "events": "Events",
248 "copyId": "Copy customer Id", 259 "copyId": "Copy customer Id",
249 - "idCopiedMessage": "Customer Id has been copied to clipboard" 260 + "idCopiedMessage": "Customer Id has been copied to clipboard",
  261 + "select-customer": "Select customer",
  262 + "no-customers-matching": "No customers matching '{{entity}}' were found.",
  263 + "customer-required": "Customer is required"
250 }, 264 },
251 "datetime": { 265 "datetime": {
252 "date-from": "Date from", 266 "date-from": "Date from",
@@ -304,7 +318,7 @@ export default angular.module('thingsboard.locale', []) @@ -304,7 +318,7 @@ export default angular.module('thingsboard.locale', [])
304 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", 318 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
305 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", 319 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
306 "select-dashboard": "Select dashboard", 320 "select-dashboard": "Select dashboard",
307 - "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.", 321 + "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.",
308 "dashboard-required": "Dashboard is required.", 322 "dashboard-required": "Dashboard is required.",
309 "select-existing": "Select existing dashboard", 323 "select-existing": "Select existing dashboard",
310 "create-new": "Create new dashboard", 324 "create-new": "Create new dashboard",
@@ -425,7 +439,7 @@ export default angular.module('thingsboard.locale', []) @@ -425,7 +439,7 @@ export default angular.module('thingsboard.locale', [])
425 "create-new-key": "Create a new one!", 439 "create-new-key": "Create a new one!",
426 "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.", 440 "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
427 "configure-alias": "Configure '{{alias}}' alias", 441 "configure-alias": "Configure '{{alias}}' alias",
428 - "no-devices-matching": "No devices matching '{{device}}' were found.", 442 + "no-devices-matching": "No devices matching '{{entity}}' were found.",
429 "alias": "Alias", 443 "alias": "Alias",
430 "alias-required": "Device alias is required.", 444 "alias-required": "Device alias is required.",
431 "remove-alias": "Remove device alias", 445 "remove-alias": "Remove device alias",
@@ -497,7 +511,8 @@ export default angular.module('thingsboard.locale', []) @@ -497,7 +511,8 @@ export default angular.module('thingsboard.locale', [])
497 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}", 511 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
498 "is-gateway": "Is gateway", 512 "is-gateway": "Is gateway",
499 "public": "Public", 513 "public": "Public",
500 - "device-public": "Device is public" 514 + "device-public": "Device is public",
  515 + "select-device": "Select device"
501 }, 516 },
502 "dialog": { 517 "dialog": {
503 "close": "Close dialog" 518 "close": "Close dialog"
@@ -535,6 +550,9 @@ export default angular.module('thingsboard.locale', []) @@ -535,6 +550,9 @@ export default angular.module('thingsboard.locale', [])
535 "type-plugin": "Plugin", 550 "type-plugin": "Plugin",
536 "type-tenant": "Tenant", 551 "type-tenant": "Tenant",
537 "type-customer": "Customer", 552 "type-customer": "Customer",
  553 + "type-user": "User",
  554 + "type-dashboard": "Dashboard",
  555 + "type-alarm": "Alarm",
538 "select-entities": "Select entities", 556 "select-entities": "Select entities",
539 "no-aliases-found": "No aliases found.", 557 "no-aliases-found": "No aliases found.",
540 "no-alias-matching": "'{{alias}}' not found.", 558 "no-alias-matching": "'{{alias}}' not found.",
@@ -672,7 +690,7 @@ export default angular.module('thingsboard.locale', []) @@ -672,7 +690,7 @@ export default angular.module('thingsboard.locale', [])
672 "system": "System", 690 "system": "System",
673 "select-plugin": "Select plugin", 691 "select-plugin": "Select plugin",
674 "plugin": "Plugin", 692 "plugin": "Plugin",
675 - "no-plugins-matching": "No plugins matching '{{plugin}}' were found.", 693 + "no-plugins-matching": "No plugins matching '{{entity}}' were found.",
676 "plugin-required": "Plugin is required.", 694 "plugin-required": "Plugin is required.",
677 "plugin-require-match": "Please select an existing plugin.", 695 "plugin-require-match": "Please select an existing plugin.",
678 "events": "Events", 696 "events": "Events",
@@ -685,6 +703,7 @@ export default angular.module('thingsboard.locale', []) @@ -685,6 +703,7 @@ export default angular.module('thingsboard.locale', [])
685 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.", 703 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
686 "copyId": "Copy plugin Id", 704 "copyId": "Copy plugin Id",
687 "idCopiedMessage": "Plugin Id has been copied to clipboard" 705 "idCopiedMessage": "Plugin Id has been copied to clipboard"
  706 +
688 }, 707 },
689 "position": { 708 "position": {
690 "top": "Top", 709 "top": "Top",
@@ -697,7 +716,24 @@ export default angular.module('thingsboard.locale', []) @@ -697,7 +716,24 @@ export default angular.module('thingsboard.locale', [])
697 "change-password": "Change Password", 716 "change-password": "Change Password",
698 "current-password": "Current password" 717 "current-password": "Current password"
699 }, 718 },
  719 + "relation": {
  720 + "relations": "Relations",
  721 + "entity-relations": "Entity relations",
  722 + "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
  723 + "type": "Type",
  724 + "to-entity-type": "Entity type",
  725 + "to-entity-name": "Entity name",
  726 + "edit": "Edit relation",
  727 + "delete": "Delete relation",
  728 + "relation-type": "Relation type",
  729 + "relation-types": {
  730 + "Contains": "Contains",
  731 + "Manages": "Manages"
  732 + },
  733 + "add": "Add relation"
  734 + },
700 "rule": { 735 "rule": {
  736 + "rule": "Rule",
701 "rules": "Rules", 737 "rules": "Rules",
702 "delete": "Delete rule", 738 "delete": "Delete rule",
703 "activate": "Activate rule", 739 "activate": "Activate rule",
@@ -749,12 +785,16 @@ export default angular.module('thingsboard.locale', []) @@ -749,12 +785,16 @@ export default angular.module('thingsboard.locale', [])
749 "rule-file": "Rule file", 785 "rule-file": "Rule file",
750 "invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.", 786 "invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.",
751 "copyId": "Copy rule Id", 787 "copyId": "Copy rule Id",
752 - "idCopiedMessage": "Rule Id has been copied to clipboard" 788 + "idCopiedMessage": "Rule Id has been copied to clipboard",
  789 + "select-rule": "Select rule",
  790 + "no-rules-matching": "No rules matching '{{entity}}' were found.",
  791 + "rule-required": "Rule is required"
753 }, 792 },
754 "rule-plugin": { 793 "rule-plugin": {
755 "management": "Rules and plugins management" 794 "management": "Rules and plugins management"
756 }, 795 },
757 "tenant": { 796 "tenant": {
  797 + "tenant": "Tenant",
758 "tenants": "Tenants", 798 "tenants": "Tenants",
759 "management": "Tenant management", 799 "management": "Tenant management",
760 "add": "Add Tenant", 800 "add": "Add Tenant",
@@ -775,7 +815,10 @@ export default angular.module('thingsboard.locale', []) @@ -775,7 +815,10 @@ export default angular.module('thingsboard.locale', [])
775 "details": "Details", 815 "details": "Details",
776 "events": "Events", 816 "events": "Events",
777 "copyId": "Copy tenant Id", 817 "copyId": "Copy tenant Id",
778 - "idCopiedMessage": "Tenant Id has been copied to clipboard" 818 + "idCopiedMessage": "Tenant Id has been copied to clipboard",
  819 + "select-tenant": "Select tenant",
  820 + "no-tenants-matching": "No tenants matching '{{entity}}' were found.",
  821 + "tenant-required": "Tenant is required"
779 }, 822 },
780 "timeinterval": { 823 "timeinterval": {
781 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }", 824 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
@@ -803,6 +846,7 @@ export default angular.module('thingsboard.locale', []) @@ -803,6 +846,7 @@ export default angular.module('thingsboard.locale', [])
803 "time-period": "Time period" 846 "time-period": "Time period"
804 }, 847 },
805 "user": { 848 "user": {
  849 + "user": "User",
806 "users": "Users", 850 "users": "Users",
807 "customer-users": "Customer Users", 851 "customer-users": "Customer Users",
808 "tenant-admins": "Tenant Admins", 852 "tenant-admins": "Tenant Admins",
@@ -828,7 +872,10 @@ export default angular.module('thingsboard.locale', []) @@ -828,7 +872,10 @@ export default angular.module('thingsboard.locale', [])
828 "last-name": "Last Name", 872 "last-name": "Last Name",
829 "description": "Description", 873 "description": "Description",
830 "default-dashboard": "Default dashboard", 874 "default-dashboard": "Default dashboard",
831 - "always-fullscreen": "Always fullscreen" 875 + "always-fullscreen": "Always fullscreen",
  876 + "select-user": "Select user",
  877 + "no-users-matching": "No users matching '{{entity}}' were found.",
  878 + "user-required": "User is required"
832 }, 879 },
833 "value": { 880 "value": {
834 "type": "Value type", 881 "type": "Value type",
@@ -261,6 +261,45 @@ pre.tb-highlight { @@ -261,6 +261,45 @@ pre.tb-highlight {
261 font-size: 16px; 261 font-size: 16px;
262 } 262 }
263 263
  264 +.tb-data-table {
  265 + md-toolbar {
  266 + z-index: 0;
  267 + }
  268 + span.no-data-found {
  269 + position: relative;
  270 + height: calc(100% - 57px);
  271 + text-transform: uppercase;
  272 + display: flex;
  273 + }
  274 + table.md-table {
  275 + tbody {
  276 + tr {
  277 + td {
  278 + &.tb-action-cell {
  279 + overflow: hidden;
  280 + text-overflow: ellipsis;
  281 + white-space: nowrap;
  282 + min-width: 72px;
  283 + max-width: 72px;
  284 + width: 72px;
  285 + .md-button {
  286 + &.md-icon-button {
  287 + margin: 0;
  288 + padding: 6px;
  289 + width: 36px;
  290 + height: 36px;
  291 + }
  292 + }
  293 + .tb-spacer {
  294 + padding-left: 38px;
  295 + }
  296 + }
  297 + }
  298 + }
  299 + }
  300 + }
  301 +}
  302 +
264 303
265 /*********************** 304 /***********************
266 * Flow 305 * Flow