Commit a708509aacbab0a723833c29b28a0775a2f6ac77

Authored by Igor Kulikov
1 parent 90ef91e3

Entity relations management.

Showing 61 changed files with 1516 additions and 145 deletions
... ... @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.*;
21 21 import org.thingsboard.server.common.data.id.EntityId;
22 22 import org.thingsboard.server.common.data.id.EntityIdFactory;
23 23 import org.thingsboard.server.common.data.relation.EntityRelation;
  24 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
24 25 import org.thingsboard.server.dao.relation.EntityRelationsQuery;
25 26 import org.thingsboard.server.exception.ThingsboardErrorCode;
26 27 import org.thingsboard.server.exception.ThingsboardException;
... ... @@ -128,6 +129,21 @@ public class EntityRelationController extends BaseController {
128 129 }
129 130
130 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 147 @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {"fromId", "fromType", "relationType"})
132 148 @ResponseBody
133 149 public List<EntityRelation> findByFrom(@RequestParam("fromId") String strFromId, @RequestParam("fromType") String strFromType
... ...
... ... @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.id.TenantId;
20 20
21 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 25 private static final long serialVersionUID = -1599722990298929275L;
26 26
... ... @@ -59,6 +59,11 @@ public class Customer extends ContactBased<CustomerId>{
59 59 this.title = title;
60 60 }
61 61
  62 + @Override
  63 + public String getName() {
  64 + return title;
  65 + }
  66 +
62 67 public JsonNode getAdditionalInfo() {
63 68 return additionalInfo;
64 69 }
... ...
... ... @@ -19,7 +19,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
19 19 import org.thingsboard.server.common.data.id.DashboardId;
20 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 24 private TenantId tenantId;
25 25 private CustomerId customerId;
... ... @@ -65,6 +65,11 @@ public class DashboardInfo extends SearchTextBased<DashboardId> {
65 65 }
66 66
67 67 @Override
  68 + public String getName() {
  69 + return title;
  70 + }
  71 +
  72 + @Override
68 73 public String getSearchText() {
69 74 return title;
70 75 }
... ...
... ... @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.id.TenantId;
21 21
22 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 26 private static final long serialVersionUID = 2807343040519543363L;
27 27
... ... @@ -64,6 +64,7 @@ public class Device extends SearchTextBased<DeviceId> {
64 64 this.customerId = customerId;
65 65 }
66 66
  67 + @Override
67 68 public String getName() {
68 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 19
20 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 24 private static final long serialVersionUID = 8057243243859922101L;
25 25
... ... @@ -50,6 +50,11 @@ public class Tenant extends ContactBased<TenantId>{
50 50 this.title = title;
51 51 }
52 52
  53 + @Override
  54 + public String getName() {
  55 + return title;
  56 + }
  57 +
53 58 public String getRegion() {
54 59 return region;
55 60 }
... ...
... ... @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.security.Authority;
22 22
23 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 27 private static final long serialVersionUID = 8250339805336035966L;
28 28
... ... @@ -77,6 +77,11 @@ public class User extends SearchTextBased<UserId> {
77 77 this.email = email;
78 78 }
79 79
  80 + @Override
  81 + public String getName() {
  82 + return email;
  83 + }
  84 +
80 85 public Authority getAuthority() {
81 86 return authority;
82 87 }
... ...
... ... @@ -18,13 +18,14 @@ package org.thingsboard.server.common.data.alarm;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.Data;
20 20 import org.thingsboard.server.common.data.BaseData;
  21 +import org.thingsboard.server.common.data.HasName;
21 22 import org.thingsboard.server.common.data.id.EntityId;
22 23
23 24 /**
24 25 * Created by ashvayka on 11.05.17.
25 26 */
26 27 @Data
27   -public class Alarm extends BaseData<AlarmId> {
  28 +public class Alarm extends BaseData<AlarmId> implements HasName {
28 29
29 30 private long startTs;
30 31 private long endTs;
... ... @@ -37,4 +38,8 @@ public class Alarm extends BaseData<AlarmId> {
37 38 private JsonNode details;
38 39 private boolean propagate;
39 40
  41 + @Override
  42 + public String getName() {
  43 + return type;
  44 + }
40 45 }
... ...
... ... @@ -16,12 +16,13 @@
16 16 package org.thingsboard.server.common.data.asset;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
  19 +import org.thingsboard.server.common.data.HasName;
19 20 import org.thingsboard.server.common.data.SearchTextBased;
20 21 import org.thingsboard.server.common.data.id.AssetId;
21 22 import org.thingsboard.server.common.data.id.CustomerId;
22 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 27 private static final long serialVersionUID = 2807343040519543363L;
27 28
... ... @@ -64,6 +65,7 @@ public class Asset extends SearchTextBased<AssetId> {
64 65 this.customerId = customerId;
65 66 }
66 67
  68 + @Override
67 69 public String getName() {
68 70 return name;
69 71 }
... ...
... ... @@ -15,13 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.common.data.plugin;
17 17
  18 +import org.thingsboard.server.common.data.HasName;
18 19 import org.thingsboard.server.common.data.SearchTextBased;
19 20 import org.thingsboard.server.common.data.id.PluginId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22
22 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 27 private static final long serialVersionUID = 1L;
27 28
... ... @@ -75,6 +76,7 @@ public class PluginMetaData extends SearchTextBased<PluginId> {
75 76 this.tenantId = tenantId;
76 77 }
77 78
  79 + @Override
78 80 public String getName() {
79 81 return name;
80 82 }
... ...
... ... @@ -47,11 +47,11 @@ public class EntityRelation {
47 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 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 17
18 18 import lombok.Data;
19 19 import lombok.ToString;
  20 +import org.thingsboard.server.common.data.HasName;
20 21 import org.thingsboard.server.common.data.SearchTextBased;
21 22 import org.thingsboard.server.common.data.id.CustomerId;
22 23 import org.thingsboard.server.common.data.id.RuleId;
... ... @@ -26,7 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
26 27 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
27 28
28 29 @Data
29   -public class RuleMetaData extends SearchTextBased<RuleId> {
  30 +public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
30 31
31 32 private static final long serialVersionUID = -5656679015122935465L;
32 33
... ... @@ -66,4 +67,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> {
66 67 return name;
67 68 }
68 69
  70 + @Override
  71 + public String getName() {
  72 + return name;
  73 + }
  74 +
69 75 }
... ...
... ... @@ -28,6 +28,10 @@ import java.util.Optional;
28 28 */
29 29 public interface AlarmService {
30 30
  31 + Alarm findAlarmById(AlarmId alarmId);
  32 +
  33 + ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
  34 +
31 35 Optional<Alarm> saveIfNotExists(Alarm alarm);
32 36
33 37 ListenableFuture<Boolean> updateAlarm(Alarm alarm);
... ...
  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.dao.alarm;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.stereotype.Service;
  21 +import org.thingsboard.server.common.data.alarm.Alarm;
  22 +import org.thingsboard.server.common.data.alarm.AlarmId;
  23 +import org.thingsboard.server.common.data.alarm.AlarmQuery;
  24 +import org.thingsboard.server.common.data.page.TimePageData;
  25 +
  26 +import java.util.Optional;
  27 +
  28 +@Service
  29 +@Slf4j
  30 +public class BaseAlarmService implements AlarmService {
  31 +
  32 + @Override
  33 + public Alarm findAlarmById(AlarmId alarmId) {
  34 + return null;
  35 + }
  36 +
  37 + @Override
  38 + public ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId) {
  39 + return null;
  40 + }
  41 +
  42 + @Override
  43 + public Optional<Alarm> saveIfNotExists(Alarm alarm) {
  44 + return null;
  45 + }
  46 +
  47 + @Override
  48 + public ListenableFuture<Boolean> updateAlarm(Alarm alarm) {
  49 + return null;
  50 + }
  51 +
  52 + @Override
  53 + public ListenableFuture<Boolean> ackAlarm(Alarm alarm) {
  54 + return null;
  55 + }
  56 +
  57 + @Override
  58 + public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId) {
  59 + return null;
  60 + }
  61 +
  62 + @Override
  63 + public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {
  64 + return null;
  65 + }
  66 +}
... ...
... ... @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
35 35 import org.thingsboard.server.common.data.page.TextPageLink;
36 36 import org.thingsboard.server.common.data.relation.EntityRelation;
37 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 39 import org.thingsboard.server.dao.exception.DataValidationException;
40 40 import org.thingsboard.server.dao.model.*;
41 41 import org.thingsboard.server.dao.relation.EntitySearchDirection;
... ... @@ -55,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
55 55
56 56 @Service
57 57 @Slf4j
58   -public class BaseAssetService extends BaseEntityService implements AssetService {
  58 +public class BaseAssetService extends AbstractEntityService implements AssetService {
59 59
60 60 @Autowired
61 61 private AssetDao assetDao;
... ...
... ... @@ -31,17 +31,15 @@ import com.google.common.util.concurrent.ListenableFuture;
31 31 import lombok.extern.slf4j.Slf4j;
32 32 import org.apache.commons.lang3.StringUtils;
33 33 import org.thingsboard.server.common.data.Customer;
34   -import org.thingsboard.server.common.data.asset.Asset;
35 34 import org.thingsboard.server.common.data.id.CustomerId;
36 35 import org.thingsboard.server.common.data.id.TenantId;
37 36 import org.thingsboard.server.common.data.page.TextPageData;
38 37 import org.thingsboard.server.common.data.page.TextPageLink;
39 38 import org.thingsboard.server.dao.dashboard.DashboardService;
40 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 41 import org.thingsboard.server.dao.exception.DataValidationException;
43 42 import org.thingsboard.server.dao.exception.IncorrectParameterException;
44   -import org.thingsboard.server.dao.model.AssetEntity;
45 43 import org.thingsboard.server.dao.model.CustomerEntity;
46 44 import org.thingsboard.server.dao.model.TenantEntity;
47 45 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -53,7 +51,7 @@ import org.springframework.stereotype.Service;
53 51 import org.thingsboard.server.dao.service.Validator;
54 52 @Service
55 53 @Slf4j
56   -public class CustomerServiceImpl extends BaseEntityService implements CustomerService {
  54 +public class CustomerServiceImpl extends AbstractEntityService implements CustomerService {
57 55
58 56 private static final String PUBLIC_CUSTOMER_TITLE = "Public";
59 57
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.dashboard;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.thingsboard.server.common.data.Dashboard;
19 20 import org.thingsboard.server.common.data.DashboardInfo;
20 21 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -27,8 +28,12 @@ public interface DashboardService {
27 28
28 29 public Dashboard findDashboardById(DashboardId dashboardId);
29 30
  31 + public ListenableFuture<Dashboard> findDashboardByIdAsync(DashboardId dashboardId);
  32 +
30 33 public DashboardInfo findDashboardInfoById(DashboardId dashboardId);
31 34
  35 + public ListenableFuture<DashboardInfo> findDashboardInfoByIdAsync(DashboardId dashboardId);
  36 +
32 37 public Dashboard saveDashboard(Dashboard dashboard);
33 38
34 39 public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
... ...
... ... @@ -17,9 +17,13 @@ package org.thingsboard.server.dao.dashboard;
17 17
18 18 import static org.thingsboard.server.dao.DaoUtil.convertDataList;
19 19 import static org.thingsboard.server.dao.DaoUtil.getData;
  20 +import static org.thingsboard.server.dao.service.Validator.validateId;
20 21
21 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 27 import lombok.extern.slf4j.Slf4j;
24 28 import org.apache.commons.lang3.StringUtils;
25 29 import org.thingsboard.server.common.data.Dashboard;
... ... @@ -30,7 +34,7 @@ import org.thingsboard.server.common.data.id.TenantId;
30 34 import org.thingsboard.server.common.data.page.TextPageData;
31 35 import org.thingsboard.server.common.data.page.TextPageLink;
32 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 38 import org.thingsboard.server.dao.exception.DataValidationException;
35 39 import org.thingsboard.server.dao.model.*;
36 40 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -42,7 +46,7 @@ import org.thingsboard.server.dao.service.Validator;
42 46
43 47 @Service
44 48 @Slf4j
45   -public class DashboardServiceImpl extends BaseEntityService implements DashboardService {
  49 +public class DashboardServiceImpl extends AbstractEntityService implements DashboardService {
46 50
47 51 @Autowired
48 52 private DashboardDao dashboardDao;
... ... @@ -65,6 +69,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
65 69 }
66 70
67 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 80 public DashboardInfo findDashboardInfoById(DashboardId dashboardId) {
69 81 log.trace("Executing findDashboardInfoById [{}]", dashboardId);
70 82 Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
... ... @@ -73,6 +85,14 @@ public class DashboardServiceImpl extends BaseEntityService implements Dashboard
73 85 }
74 86
75 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 96 public Dashboard saveDashboard(Dashboard dashboard) {
77 97 log.trace("Executing saveDashboard [{}]", dashboard);
78 98 dashboardValidator.validate(dashboard);
... ...
... ... @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
37 37 import org.thingsboard.server.common.data.security.DeviceCredentials;
38 38 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
39 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 41 import org.thingsboard.server.dao.exception.DataValidationException;
42 42 import org.thingsboard.server.dao.model.CustomerEntity;
43 43 import org.thingsboard.server.dao.model.DeviceEntity;
... ... @@ -58,7 +58,7 @@ import static org.thingsboard.server.dao.service.Validator.*;
58 58
59 59 @Service
60 60 @Slf4j
61   -public class DeviceServiceImpl extends BaseEntityService implements DeviceService {
  61 +public class DeviceServiceImpl extends AbstractEntityService implements DeviceService {
62 62
63 63 @Autowired
64 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 15 */
16 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 21 import lombok.extern.slf4j.Slf4j;
19 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 38 * Created by ashvayka on 04.05.17.
25 39 */
  40 +@Service
26 41 @Slf4j
27   -public class BaseEntityService {
  42 +public class BaseEntityService extends AbstractEntityService implements EntityService {
28 43
29 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 22 import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.stereotype.Service;
24 24 import org.thingsboard.server.common.data.id.PluginId;
25   -import org.thingsboard.server.common.data.id.RuleId;
26 25 import org.thingsboard.server.common.data.id.TenantId;
27 26 import org.thingsboard.server.common.data.page.TextPageData;
28 27 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -30,9 +29,8 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
30 29 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
31 30 import org.thingsboard.server.common.data.plugin.ComponentType;
32 31 import org.thingsboard.server.common.data.plugin.PluginMetaData;
33   -import org.thingsboard.server.common.data.rule.RuleMetaData;
34 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 34 import org.thingsboard.server.dao.exception.DataValidationException;
37 35 import org.thingsboard.server.dao.exception.DatabaseException;
38 36 import org.thingsboard.server.dao.exception.IncorrectParameterException;
... ... @@ -55,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
55 53
56 54 @Service
57 55 @Slf4j
58   -public class BasePluginService extends BaseEntityService implements PluginService {
  56 +public class BasePluginService extends AbstractEntityService implements PluginService {
59 57
60 58 //TODO: move to a better place.
61 59 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
... ...
... ... @@ -23,9 +23,24 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.stereotype.Service;
25 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 32 import org.thingsboard.server.common.data.id.EntityId;
  33 +import org.thingsboard.server.common.data.id.UUIDBased;
27 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 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 45 import javax.annotation.Nullable;
31 46 import java.util.*;
... ... @@ -41,6 +56,9 @@ public class BaseRelationService implements RelationService {
41 56 @Autowired
42 57 private RelationDao relationDao;
43 58
  59 + @Autowired
  60 + private EntityService entityService;
  61 +
44 62 @Override
45 63 public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType) {
46 64 log.trace("Executing checkRelation [{}][{}][{}]", from, to, relationType);
... ... @@ -100,6 +118,31 @@ public class BaseRelationService implements RelationService {
100 118 }
101 119
102 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 146 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
104 147 log.trace("Executing findByFromAndType [{}][{}]", from, relationType);
105 148 validate(from);
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.relation;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.id.EntityId;
20 20 import org.thingsboard.server.common.data.relation.EntityRelation;
  21 +import org.thingsboard.server.common.data.relation.EntityRelationInfo;
21 22
22 23 import java.util.List;
23 24
... ... @@ -38,6 +39,8 @@ public interface RelationService {
38 39
39 40 ListenableFuture<List<EntityRelation>> findByFrom(EntityId from);
40 41
  42 + ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from);
  43 +
41 44 ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType);
42 45
43 46 ListenableFuture<List<EntityRelation>> findByTo(EntityId to);
... ...
... ... @@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j;
23 23 import org.apache.commons.lang3.StringUtils;
24 24 import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.stereotype.Service;
26   -import org.thingsboard.server.common.data.asset.Asset;
27 26 import org.thingsboard.server.common.data.id.RuleId;
28 27 import org.thingsboard.server.common.data.id.TenantId;
29 28 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -34,11 +33,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
34 33 import org.thingsboard.server.common.data.plugin.PluginMetaData;
35 34 import org.thingsboard.server.common.data.rule.RuleMetaData;
36 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 37 import org.thingsboard.server.dao.exception.DataValidationException;
39 38 import org.thingsboard.server.dao.exception.DatabaseException;
40 39 import org.thingsboard.server.dao.exception.IncorrectParameterException;
41   -import org.thingsboard.server.dao.model.AssetEntity;
42 40 import org.thingsboard.server.dao.model.RuleMetaDataEntity;
43 41 import org.thingsboard.server.dao.plugin.PluginService;
44 42 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -58,7 +56,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
58 56
59 57 @Service
60 58 @Slf4j
61   -public class BaseRuleService extends BaseEntityService implements RuleService {
  59 +public class BaseRuleService extends AbstractEntityService implements RuleService {
62 60
63 61 private final TenantId systemTenantId = new TenantId(NULL_UUID);
64 62
... ...
... ... @@ -26,7 +26,6 @@ import com.google.common.util.concurrent.Futures;
26 26 import com.google.common.util.concurrent.ListenableFuture;
27 27 import lombok.extern.slf4j.Slf4j;
28 28 import org.apache.commons.lang3.StringUtils;
29   -import org.thingsboard.server.common.data.Customer;
30 29 import org.thingsboard.server.common.data.Tenant;
31 30 import org.thingsboard.server.common.data.id.TenantId;
32 31 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -34,9 +33,8 @@ import org.thingsboard.server.common.data.page.TextPageLink;
34 33 import org.thingsboard.server.dao.customer.CustomerService;
35 34 import org.thingsboard.server.dao.dashboard.DashboardService;
36 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 37 import org.thingsboard.server.dao.exception.DataValidationException;
39   -import org.thingsboard.server.dao.model.CustomerEntity;
40 38 import org.thingsboard.server.dao.model.TenantEntity;
41 39 import org.thingsboard.server.dao.plugin.PluginService;
42 40 import org.thingsboard.server.dao.rule.RuleService;
... ... @@ -50,7 +48,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
50 48
51 49 @Service
52 50 @Slf4j
53   -public class TenantServiceImpl extends BaseEntityService implements TenantService {
  51 +public class TenantServiceImpl extends AbstractEntityService implements TenantService {
54 52
55 53 private static final String DEFAULT_TENANT_REGION = "Global";
56 54
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.dao.user;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import org.thingsboard.server.common.data.User;
19 20 import org.thingsboard.server.common.data.id.CustomerId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -27,6 +28,8 @@ public interface UserService {
27 28
28 29 public User findUserById(UserId userId);
29 30
  31 + public ListenableFuture<User> findUserByIdAsync(UserId userId);
  32 +
30 33 public User findUserByEmail(String email);
31 34
32 35 public User saveUser(User user);
... ...
... ... @@ -23,6 +23,9 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
23 23
24 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 29 import lombok.extern.slf4j.Slf4j;
27 30 import org.apache.commons.lang3.RandomStringUtils;
28 31 import org.apache.commons.lang3.StringUtils;
... ... @@ -35,7 +38,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
35 38 import org.thingsboard.server.common.data.security.Authority;
36 39 import org.thingsboard.server.common.data.security.UserCredentials;
37 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 42 import org.thingsboard.server.dao.exception.DataValidationException;
40 43 import org.thingsboard.server.dao.exception.IncorrectParameterException;
41 44 import org.thingsboard.server.dao.model.*;
... ... @@ -47,7 +50,7 @@ import org.springframework.stereotype.Service;
47 50
48 51 @Service
49 52 @Slf4j
50   -public class UserServiceImpl extends BaseEntityService implements UserService {
  53 +public class UserServiceImpl extends AbstractEntityService implements UserService {
51 54
52 55 @Autowired
53 56 private UserDao userDao;
... ... @@ -78,6 +81,14 @@ public class UserServiceImpl extends BaseEntityService implements UserService {
78 81 }
79 82
80 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 92 public User saveUser(User user) {
82 93 log.trace("Executing saveUser [{}]", user);
83 94 userValidator.validate(user);
... ...
... ... @@ -25,6 +25,7 @@ function EntityRelationService($http, $q) {
25 25 deleteRelation: deleteRelation,
26 26 deleteRelations: deleteRelations,
27 27 findByFrom: findByFrom,
  28 + findInfoByFrom: findInfoByFrom,
28 29 findByFromAndType: findByFromAndType,
29 30 findByTo: findByTo,
30 31 findByToAndType: findByToAndType,
... ... @@ -84,6 +85,18 @@ function EntityRelationService($http, $q) {
84 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 100 function findByFromAndType(fromId, fromType, relationType) {
88 101 var deferred = $q.defer();
89 102 var url = '/api/relations?fromId=' + fromId;
... ...
... ... @@ -20,14 +20,13 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
20 20 .name;
21 21
22 22 /*@ngInject*/
23   -function EntityService($http, $q, $filter, $translate, userService, deviceService,
  23 +function EntityService($http, $q, $filter, $translate, $log, userService, deviceService,
24 24 assetService, tenantService, customerService,
25   - ruleService, pluginService, entityRelationService, attributeService, types, utils) {
  25 + ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) {
26 26 var service = {
27 27 getEntity: getEntity,
28 28 getEntities: getEntities,
29 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30   - entityName: entityName,
31 30 processEntityAliases: processEntityAliases,
32 31 getEntityKeys: getEntityKeys,
33 32 checkEntityAlias: checkEntityAlias,
... ... @@ -63,6 +62,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
63 62 case types.entityType.plugin:
64 63 promise = pluginService.getPlugin(entityId);
65 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 75 return promise;
68 76 }
... ... @@ -134,6 +142,15 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
134 142 case types.entityType.plugin:
135 143 promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds);
136 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 155 return promise;
139 156 }
... ... @@ -141,34 +158,38 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
141 158 function getEntities(entityType, entityIds, config) {
142 159 var deferred = $q.defer();
143 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 173 return deferred.promise;
153 174 }
154 175
155   - function getEntitiesByPageLinkPromise(entityType, pageLink, config) {
  176 + function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
156 177 var promise;
157 178 var user = userService.getCurrentUser();
158 179 var customerId = user.customerId;
159 180 switch (entityType) {
160 181 case types.entityType.device:
161 182 if (user.authority === 'CUSTOMER_USER') {
162   - promise = deviceService.getCustomerDevices(customerId, pageLink, false, config);
  183 + promise = deviceService.getCustomerDevices(customerId, pageLink, false, config, subType);
163 184 } else {
164   - promise = deviceService.getTenantDevices(pageLink, false, config);
  185 + promise = deviceService.getTenantDevices(pageLink, false, config, subType);
165 186 }
166 187 break;
167 188 case types.entityType.asset:
168 189 if (user.authority === 'CUSTOMER_USER') {
169   - promise = assetService.getCustomerAssets(customerId, pageLink, false, config);
  190 + promise = assetService.getCustomerAssets(customerId, pageLink, false, config, subType);
170 191 } else {
171   - promise = assetService.getTenantAssets(pageLink, false, config);
  192 + promise = assetService.getTenantAssets(pageLink, false, config, subType);
172 193 }
173 194 break;
174 195 case types.entityType.tenant:
... ... @@ -183,48 +204,48 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
183 204 case types.entityType.plugin:
184 205 promise = pluginService.getAllPlugins(pageLink);
185 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 221 return promise;
188 222 }
189 223
190   - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config) {
  224 + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
191 225 var deferred = $q.defer();
192 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 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 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 251 function entitiesToEntitiesInfo(entityType, entities) {
... ...
... ... @@ -55,4 +55,10 @@
55 55 default-event-type="{{vm.types.eventType.alarm.value}}">
56 56 </tb-event-table>
57 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 64 </tb-grid>
... ...
... ... @@ -98,7 +98,10 @@ export default angular.module('thingsboard.types', [])
98 98 rule: "RULE",
99 99 plugin: "PLUGIN",
100 100 tenant: "TENANT",
101   - customer: "CUSTOMER"
  101 + customer: "CUSTOMER",
  102 + user: "USER",
  103 + dashboard: "DASHBOARD",
  104 + alarm: "ALARM"
102 105 },
103 106 entitySearchDirection: {
104 107 from: "FROM",
... ...
... ... @@ -108,7 +108,8 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
108 108 guid: guid,
109 109 isLocalUrl: isLocalUrl,
110 110 validateDatasources: validateDatasources,
111   - createKey: createKey
  111 + createKey: createKey,
  112 + entityTypeName: entityTypeName
112 113 }
113 114
114 115 return service;
... ... @@ -346,4 +347,27 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
346 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 34 </md-item-template>
35 35 <md-not-found>
36 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 38 </div>
39 39 </md-not-found>
40 40 <div ng-messages="theForm.dashboard.$error">
... ...
... ... @@ -34,7 +34,7 @@
34 34 </md-item-template>
35 35 <md-not-found>
36 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 38 </div>
39 39 </md-not-found>
40 40 </md-autocomplete>
... ...
... ... @@ -109,7 +109,7 @@ export default function EntityStateController($scope, $location, $state, $stateP
109 109 if (params && params.entityId && params.entityId.id && params.entityId.entityType) {
110 110 entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then(
111 111 function success(entity) {
112   - var entityName = entityService.entityName(params.entityId.entityType, entity);
  112 + var entityName = entity.name;
113 113 deferred.resolve(entityName);
114 114 },
115 115 function fail() {
... ...
... ... @@ -128,9 +128,9 @@
128 128 <table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
129 129 <thead md-head md-order="query.order" md-on-reorder="onReorder">
130 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 134 </tr>
135 135 </thead>
136 136 <tbody md-body>
... ...
... ... @@ -110,7 +110,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
110 110 entityAlias.changed = false;
111 111 }
112 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 32
33 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 35 scope.fetchEntities = function(searchText, limit) {
44 36 var deferred = $q.defer();
45 37 entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {
... ...
... ... @@ -33,7 +33,7 @@
33 33 md-min-length="0"
34 34 placeholder="{{ 'entity.entity-list' | translate }}">
35 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 37 </md-item-template>
38 38 <md-not-found>
39 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 +}
\ No newline at end of file
... ...
  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>
\ No newline at end of file
... ...
... ... @@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
23 23 /* eslint-enable import/no-unresolved, import/default */
24 24
25 25 /*@ngInject*/
26   -export default function EntityTypeSelect($compile, $templateCache, userService, types) {
  26 +export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
27 27
28 28 var linker = function (scope, element, attrs, ngModelCtrl) {
29 29 var template = $templateCache.get(entityTypeSelectTemplate);
... ... @@ -51,10 +51,12 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
51 51 scope.entityTypes.customer = types.entityType.customer;
52 52 scope.entityTypes.rule = types.entityType.rule;
53 53 scope.entityTypes.plugin = types.entityType.plugin;
  54 + scope.entityTypes.dashboard = types.entityType.dashboard;
54 55 break;
55 56 case 'CUSTOMER_USER':
56 57 scope.entityTypes.device = types.entityType.device;
57 58 scope.entityTypes.asset = types.entityType.asset;
  59 + scope.entityTypes.dashboard = types.entityType.dashboard;
58 60 break;
59 61 }
60 62
... ... @@ -67,20 +69,7 @@ export default function EntityTypeSelect($compile, $templateCache, userService,
67 69 }
68 70
69 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 75 scope.updateValidity = function () {
... ...
... ... @@ -18,12 +18,15 @@ import EntityAliasesController from './entity-aliases.controller';
18 18 import EntityTypeSelectDirective from './entity-type-select.directive';
19 19 import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
20 20 import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
  21 +import EntityAutocompleteDirective from './entity-autocomplete.directive';
  22 +import EntitySelectDirective from './entity-select.directive';
21 23 import EntityFilterDirective from './entity-filter.directive';
22 24 import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
23 25 import AliasesEntitySelectDirective from './aliases-entity-select.directive';
24 26 import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
25 27 import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
26 28 import AttributeTableDirective from './attribute/attribute-table.directive';
  29 +import RelationTableDirective from './relation/relation-table.directive';
27 30
28 31 export default angular.module('thingsboard.entity', [])
29 32 .controller('EntityAliasesController', EntityAliasesController)
... ... @@ -33,7 +36,10 @@ export default angular.module('thingsboard.entity', [])
33 36 .directive('tbEntityTypeSelect', EntityTypeSelectDirective)
34 37 .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
35 38 .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
  39 + .directive('tbEntityAutocomplete', EntityAutocompleteDirective)
  40 + .directive('tbEntitySelect', EntitySelectDirective)
36 41 .directive('tbEntityFilter', EntityFilterDirective)
37 42 .directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
38 43 .directive('tbAttributeTable', AttributeTableDirective)
  44 + .directive('tbRelationTable', RelationTableDirective)
39 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 235 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
236 236 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
237 237 "select-dashboard": "Seleccionar panel",
238   - "no-dashboards-matching": "Panel '{{dashboard}}' no encontrado.",
  238 + "no-dashboards-matching": "Panel '{{entity}}' no encontrado.",
239 239 "dashboard-required": "Panel requerido.",
240 240 "select-existing": "Seleccionar paneles existentes",
241 241 "create-new": "Crear nuevo panel",
... ... @@ -330,7 +330,7 @@
330 330 "create-new-key": "Crear nueva clave!",
331 331 "duplicate-alias-error": "Alias duplicado '{{alias}}'.<br> El alias de los dispositivos deben ser únicos dentro del panel.",
332 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 334 "alias": "Alias",
335 335 "alias-required": "Alias de dispositivo requerido.",
336 336 "remove-alias": "Eliminar alias",
... ... @@ -529,7 +529,7 @@
529 529 "system": "Sistema",
530 530 "select-plugin": "plugin",
531 531 "plugin": "Plugin",
532   - "no-plugins-matching": "No se encontraron plugins: '{{plugin}}'",
  532 + "no-plugins-matching": "No se encontraron plugins: '{{entity}}'",
533 533 "plugin-required": "Plugin requerido.",
534 534 "plugin-require-match": "Por favor, elija un plugin existente.",
535 535 "events": "Eventos",
... ...
... ... @@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) {
218 218 "unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?",
219 219 "unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.",
220 220 "select-dashboard": "대시보드 선택",
221   - "no-dashboards-matching": "'{{dashboard}}'와 일치하는 대시보드가 없습니다.",
  221 + "no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.",
222 222 "dashboard-required": "대시보드를 입력하세요.",
223 223 "select-existing": "기존 대시보드 선택",
224 224 "create-new": "대시보드 생성",
... ... @@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) {
305 305 "create-new-key": "새로 만들기!",
306 306 "duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.<br> 디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.",
307 307 "configure-alias": "'{{alias}}' 앨리어스 구성",
308   - "no-devices-matching": "'{{device}}'와 일치하는 디바이스를 찾을 수 없습니다.",
  308 + "no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.",
309 309 "alias": "앨리어스",
310 310 "alias-required": "디바이스 앨리어스를 입력하세요.",
311 311 "remove-alias": "디바이스 앨리어스 삭제",
... ... @@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) {
496 496 "system": "시스템",
497 497 "select-plugin": "플러그인 선택",
498 498 "plugin": "플러그인",
499   - "no-plugins-matching": "'{{plugin}}'과 일치하는 플러그인을 찾을 수 없습니다.",
  499 + "no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.",
500 500 "plugin-required": "플러그인을 입력하세요.",
501 501 "plugin-require-match": "기존의 플러그인을 선택해주세요.",
502 502 "events": "이벤트",
... ...
... ... @@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) {
235 235 "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard",
236 236 "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard",
237 237 "select-dashboard": "Выберите дашборд",
238   - "no-dashboards-matching": "Дашборд '{{dashboard}}' не найден.",
  238 + "no-dashboards-matching": "Дашборд '{{entity}}' не найден.",
239 239 "dashboard-required": "Дашборд обязателен.",
240 240 "select-existing": "Выберите существующий дашборд",
241 241 "create-new": "Создать новый дашборд",
... ... @@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) {
330 330 "create-new-key": "Создать новый!",
331 331 "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.<br>В рамках дашборда псевдонимы устройств должны быть уникальными.",
332 332 "configure-alias": "Конфигурировать '{{alias}}' псевдоним",
333   - "no-devices-matching": "Устройство '{{device}}' не найдено.",
  333 + "no-devices-matching": "Устройство '{{entity}}' не найдено.",
334 334 "alias": "Псевдоним",
335 335 "alias-required": "Псевдоним устройства обязателен.",
336 336 "remove-alias": "Удалить псевдоним устройства",
... ... @@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) {
529 529 "system": "Системный",
530 530 "select-plugin": "Выберите плагин",
531 531 "plugin": "Плагин",
532   - "no-plugins-matching": "Плагин '{{plugin}}' не найден.",
  532 + "no-plugins-matching": "Плагин '{{entity}}' не найден.",
533 533 "plugin-required": "Плагин обязателен.",
534 534 "plugin-require-match": "Пожалуйста, выберите существующий плагин.",
535 535 "events": "События",
... ...
... ... @@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) {
235 235 "socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
236 236 "socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持",
237 237 "select-dashboard" : "选择仪表板",
238   - "no-dashboards-matching" : "找不到符合 '{{dashboard}}' 的仪表板。",
  238 + "no-dashboards-matching" : "找不到符合 '{{entity}}' 的仪表板。",
239 239 "dashboard-required" : "仪表板是必需的。",
240 240 "select-existing" : "选择现有仪表板",
241 241 "create-new" : "创建新的仪表板",
... ... @@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) {
330 330 "create-new-key": "创建一个新的!",
331 331 "duplicate-alias-error" : "找到重复别名 '{{alias}}'。 <br> 设备别名必须是唯一的。",
332 332 "configure-alias" : "配置 '{{alias}}' 别名",
333   - "no-devices-matching" : "找不到与 '{{device}}' 匹配的设备。",
  333 + "no-devices-matching" : "找不到与 '{{entity}}' 匹配的设备。",
334 334 "alias" : "别名",
335 335 "alias-required" : "需要设备别名。",
336 336 "remove-alias": "删除设备别名",
... ... @@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) {
529 529 "system" : "系统",
530 530 "select-plugin" : "选择插件",
531 531 "plugin" : "插件",
532   - "no-plugins-matching" : "没有找到匹配'{{plugin}}'的插件。",
  532 + "no-plugins-matching" : "没有找到匹配'{{entity}}'的插件。",
533 533 "plugin-required" : "插件是必需的。",
534 534 "plugin-require-match" : "请选择一个现有的插件。",
535 535 "events" : "事件",
... ...
... ... @@ -106,6 +106,12 @@ export default angular.module('thingsboard.locale', [])
106 106 "enable-tls": "Enable TLS",
107 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 115 "asset": {
110 116 "asset": "Asset",
111 117 "assets": "Assets",
... ... @@ -157,7 +163,10 @@ export default angular.module('thingsboard.locale', [])
157 163 "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?",
158 164 "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.",
159 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 171 "attribute": {
163 172 "attributes": "Attributes",
... ... @@ -169,6 +178,7 @@ export default angular.module('thingsboard.locale', [])
169 178 "scope-shared": "Shared attributes",
170 179 "add": "Add attribute",
171 180 "key": "Key",
  181 + "last-update-time": "Last update time",
172 182 "key-required": "Attribute key is required.",
173 183 "value": "Value",
174 184 "value-required": "Attribute value is required.",
... ... @@ -210,6 +220,7 @@ export default angular.module('thingsboard.locale', [])
210 220 "enter-search": "Enter search"
211 221 },
212 222 "customer": {
  223 + "customer": "Customer",
213 224 "customers": "Customers",
214 225 "management": "Customer management",
215 226 "dashboard": "Customer Dashboard",
... ... @@ -246,7 +257,10 @@ export default angular.module('thingsboard.locale', [])
246 257 "details": "Details",
247 258 "events": "Events",
248 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 265 "datetime": {
252 266 "date-from": "Date from",
... ... @@ -304,7 +318,7 @@ export default angular.module('thingsboard.locale', [])
304 318 "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
305 319 "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
306 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 322 "dashboard-required": "Dashboard is required.",
309 323 "select-existing": "Select existing dashboard",
310 324 "create-new": "Create new dashboard",
... ... @@ -425,7 +439,7 @@ export default angular.module('thingsboard.locale', [])
425 439 "create-new-key": "Create a new one!",
426 440 "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
427 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 443 "alias": "Alias",
430 444 "alias-required": "Device alias is required.",
431 445 "remove-alias": "Remove device alias",
... ... @@ -497,7 +511,8 @@ export default angular.module('thingsboard.locale', [])
497 511 "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
498 512 "is-gateway": "Is gateway",
499 513 "public": "Public",
500   - "device-public": "Device is public"
  514 + "device-public": "Device is public",
  515 + "select-device": "Select device"
501 516 },
502 517 "dialog": {
503 518 "close": "Close dialog"
... ... @@ -535,6 +550,9 @@ export default angular.module('thingsboard.locale', [])
535 550 "type-plugin": "Plugin",
536 551 "type-tenant": "Tenant",
537 552 "type-customer": "Customer",
  553 + "type-user": "User",
  554 + "type-dashboard": "Dashboard",
  555 + "type-alarm": "Alarm",
538 556 "select-entities": "Select entities",
539 557 "no-aliases-found": "No aliases found.",
540 558 "no-alias-matching": "'{{alias}}' not found.",
... ... @@ -672,7 +690,7 @@ export default angular.module('thingsboard.locale', [])
672 690 "system": "System",
673 691 "select-plugin": "Select plugin",
674 692 "plugin": "Plugin",
675   - "no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
  693 + "no-plugins-matching": "No plugins matching '{{entity}}' were found.",
676 694 "plugin-required": "Plugin is required.",
677 695 "plugin-require-match": "Please select an existing plugin.",
678 696 "events": "Events",
... ... @@ -685,6 +703,7 @@ export default angular.module('thingsboard.locale', [])
685 703 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
686 704 "copyId": "Copy plugin Id",
687 705 "idCopiedMessage": "Plugin Id has been copied to clipboard"
  706 +
688 707 },
689 708 "position": {
690 709 "top": "Top",
... ... @@ -697,7 +716,24 @@ export default angular.module('thingsboard.locale', [])
697 716 "change-password": "Change Password",
698 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 735 "rule": {
  736 + "rule": "Rule",
701 737 "rules": "Rules",
702 738 "delete": "Delete rule",
703 739 "activate": "Activate rule",
... ... @@ -749,12 +785,16 @@ export default angular.module('thingsboard.locale', [])
749 785 "rule-file": "Rule file",
750 786 "invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.",
751 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 793 "rule-plugin": {
755 794 "management": "Rules and plugins management"
756 795 },
757 796 "tenant": {
  797 + "tenant": "Tenant",
758 798 "tenants": "Tenants",
759 799 "management": "Tenant management",
760 800 "add": "Add Tenant",
... ... @@ -775,7 +815,10 @@ export default angular.module('thingsboard.locale', [])
775 815 "details": "Details",
776 816 "events": "Events",
777 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 823 "timeinterval": {
781 824 "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
... ... @@ -803,6 +846,7 @@ export default angular.module('thingsboard.locale', [])
803 846 "time-period": "Time period"
804 847 },
805 848 "user": {
  849 + "user": "User",
806 850 "users": "Users",
807 851 "customer-users": "Customer Users",
808 852 "tenant-admins": "Tenant Admins",
... ... @@ -828,7 +872,10 @@ export default angular.module('thingsboard.locale', [])
828 872 "last-name": "Last Name",
829 873 "description": "Description",
830 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 880 "value": {
834 881 "type": "Value type",
... ...
... ... @@ -261,6 +261,45 @@ pre.tb-highlight {
261 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 305 * Flow
... ...