Commit 44c28b5b0f64ff95e39783087775a5a2745bb9a2

Authored by Volodymyr Babak
2 parents df4f5f9a 0726fbb5

Merge remote-tracking branch 'upstream/master' into dao-refactoring-vs

Showing 67 changed files with 2757 additions and 752 deletions

Too many changes to show.

To preserve performance only 67 of 120 files are displayed.

@@ -22,10 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -22,10 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
22 import org.springframework.web.bind.annotation.*; 22 import org.springframework.web.bind.annotation.*;
23 import org.thingsboard.server.common.data.Customer; 23 import org.thingsboard.server.common.data.Customer;
24 import org.thingsboard.server.common.data.Event; 24 import org.thingsboard.server.common.data.Event;
25 -import org.thingsboard.server.common.data.alarm.Alarm;  
26 -import org.thingsboard.server.common.data.alarm.AlarmId;  
27 -import org.thingsboard.server.common.data.alarm.AlarmQuery;  
28 -import org.thingsboard.server.common.data.alarm.AlarmStatus; 25 +import org.thingsboard.server.common.data.alarm.*;
29 import org.thingsboard.server.common.data.asset.Asset; 26 import org.thingsboard.server.common.data.asset.Asset;
30 import org.thingsboard.server.common.data.id.*; 27 import org.thingsboard.server.common.data.id.*;
31 import org.thingsboard.server.common.data.page.TextPageData; 28 import org.thingsboard.server.common.data.page.TextPageData;
@@ -61,6 +58,19 @@ public class AlarmController extends BaseController { @@ -61,6 +58,19 @@ public class AlarmController extends BaseController {
61 } 58 }
62 59
63 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 60 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  61 + @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET)
  62 + @ResponseBody
  63 + public AlarmInfo getAlarmInfoById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
  64 + checkParameter("alarmId", strAlarmId);
  65 + try {
  66 + AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
  67 + return checkAlarmInfoId(alarmId);
  68 + } catch (Exception e) {
  69 + throw handleException(e);
  70 + }
  71 + }
  72 +
  73 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
64 @RequestMapping(value = "/alarm", method = RequestMethod.POST) 74 @RequestMapping(value = "/alarm", method = RequestMethod.POST)
65 @ResponseBody 75 @ResponseBody
66 public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { 76 public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
@@ -103,24 +113,31 @@ public class AlarmController extends BaseController { @@ -103,24 +113,31 @@ public class AlarmController extends BaseController {
103 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 113 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
104 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) 114 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
105 @ResponseBody 115 @ResponseBody
106 - public TimePageData<Alarm> getAlarms( 116 + public TimePageData<AlarmInfo> getAlarms(
107 @PathVariable("entityType") String strEntityType, 117 @PathVariable("entityType") String strEntityType,
108 @PathVariable("entityId") String strEntityId, 118 @PathVariable("entityId") String strEntityId,
  119 + @RequestParam(required = false) String searchStatus,
109 @RequestParam(required = false) String status, 120 @RequestParam(required = false) String status,
110 @RequestParam int limit, 121 @RequestParam int limit,
111 @RequestParam(required = false) Long startTime, 122 @RequestParam(required = false) Long startTime,
112 @RequestParam(required = false) Long endTime, 123 @RequestParam(required = false) Long endTime,
113 @RequestParam(required = false, defaultValue = "false") boolean ascOrder, 124 @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
114 - @RequestParam(required = false) String offset 125 + @RequestParam(required = false) String offset,
  126 + @RequestParam(required = false) Boolean fetchOriginator
115 ) throws ThingsboardException { 127 ) throws ThingsboardException {
116 checkParameter("EntityId", strEntityId); 128 checkParameter("EntityId", strEntityId);
117 checkParameter("EntityType", strEntityType); 129 checkParameter("EntityType", strEntityType);
118 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); 130 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
  131 + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus);
119 AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); 132 AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
  133 + if (alarmSearchStatus != null && alarmStatus != null) {
  134 + throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
  135 + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  136 + }
120 checkEntityId(entityId); 137 checkEntityId(entityId);
121 try { 138 try {
122 TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); 139 TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
123 - return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get()); 140 + return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
124 } catch (Exception e) { 141 } catch (Exception e) {
125 throw handleException(e); 142 throw handleException(e);
126 } 143 }
@@ -25,6 +25,7 @@ import org.thingsboard.server.actors.service.ActorService; @@ -25,6 +25,7 @@ import org.thingsboard.server.actors.service.ActorService;
25 import org.thingsboard.server.common.data.*; 25 import org.thingsboard.server.common.data.*;
26 import org.thingsboard.server.common.data.alarm.Alarm; 26 import org.thingsboard.server.common.data.alarm.Alarm;
27 import org.thingsboard.server.common.data.alarm.AlarmId; 27 import org.thingsboard.server.common.data.alarm.AlarmId;
  28 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
28 import org.thingsboard.server.common.data.asset.Asset; 29 import org.thingsboard.server.common.data.asset.Asset;
29 import org.thingsboard.server.common.data.id.*; 30 import org.thingsboard.server.common.data.id.*;
30 import org.thingsboard.server.common.data.page.TextPageLink; 31 import org.thingsboard.server.common.data.page.TextPageLink;
@@ -349,6 +350,17 @@ public abstract class BaseController { @@ -349,6 +350,17 @@ public abstract class BaseController {
349 } 350 }
350 } 351 }
351 352
  353 + AlarmInfo checkAlarmInfoId(AlarmId alarmId) throws ThingsboardException {
  354 + try {
  355 + validateId(alarmId, "Incorrect alarmId " + alarmId);
  356 + AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(alarmId).get();
  357 + checkAlarm(alarmInfo);
  358 + return alarmInfo;
  359 + } catch (Exception e) {
  360 + throw handleException(e, false);
  361 + }
  362 + }
  363 +
352 protected void checkAlarm(Alarm alarm) throws ThingsboardException { 364 protected void checkAlarm(Alarm alarm) throws ThingsboardException {
353 checkNotNull(alarm); 365 checkNotNull(alarm);
354 checkTenantId(alarm.getTenantId()); 366 checkTenantId(alarm.getTenantId());
@@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController { @@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
250 } 250 }
251 } 251 }
252 252
  253 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  254 + @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
  255 + @ResponseBody
  256 + public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
  257 + checkNotNull(query);
  258 + checkNotNull(query.getParameters());
  259 + checkNotNull(query.getFilters());
  260 + checkEntityId(query.getParameters().getEntityId());
  261 + try {
  262 + return checkNotNull(relationService.findInfoByQuery(query).get());
  263 + } catch (Exception e) {
  264 + throw handleException(e);
  265 + }
  266 + }
  267 +
253 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) { 268 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
254 RelationTypeGroup result = defaultValue; 269 RelationTypeGroup result = defaultValue;
255 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) { 270 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
@@ -53,6 +53,22 @@ public class Alarm extends BaseData<AlarmId> implements HasName { @@ -53,6 +53,22 @@ public class Alarm extends BaseData<AlarmId> implements HasName {
53 super(id); 53 super(id);
54 } 54 }
55 55
  56 + public Alarm(Alarm alarm) {
  57 + super(alarm.getId());
  58 + this.createdTime = alarm.getCreatedTime();
  59 + this.tenantId = alarm.getTenantId();
  60 + this.type = alarm.getType();
  61 + this.originator = alarm.getOriginator();
  62 + this.severity = alarm.getSeverity();
  63 + this.status = alarm.getStatus();
  64 + this.startTs = alarm.getStartTs();
  65 + this.endTs = alarm.getEndTs();
  66 + this.ackTs = alarm.getAckTs();
  67 + this.clearTs = alarm.getClearTs();
  68 + this.details = alarm.getDetails();
  69 + this.propagate = alarm.isPropagate();
  70 + }
  71 +
56 @Override 72 @Override
57 @JsonProperty(access = JsonProperty.Access.READ_ONLY) 73 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
58 public String getName() { 74 public String getName() {
  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.alarm;
  17 +
  18 +public class AlarmInfo extends Alarm {
  19 +
  20 + private static final long serialVersionUID = 2807343093519543363L;
  21 +
  22 + private String originatorName;
  23 +
  24 + public AlarmInfo() {
  25 + super();
  26 + }
  27 +
  28 + public AlarmInfo(Alarm alarm) {
  29 + super(alarm);
  30 + }
  31 +
  32 + public String getOriginatorName() {
  33 + return originatorName;
  34 + }
  35 +
  36 + public void setOriginatorName(String originatorName) {
  37 + this.originatorName = originatorName;
  38 + }
  39 +
  40 + @Override
  41 + public boolean equals(Object o) {
  42 + if (this == o) return true;
  43 + if (o == null || getClass() != o.getClass()) return false;
  44 + if (!super.equals(o)) return false;
  45 +
  46 + AlarmInfo alarmInfo = (AlarmInfo) o;
  47 +
  48 + return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null;
  49 +
  50 + }
  51 +
  52 + @Override
  53 + public int hashCode() {
  54 + int result = super.hashCode();
  55 + result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0);
  56 + return result;
  57 + }
  58 +}
@@ -32,6 +32,8 @@ public class AlarmQuery { @@ -32,6 +32,8 @@ public class AlarmQuery {
32 32
33 private EntityId affectedEntityId; 33 private EntityId affectedEntityId;
34 private TimePageLink pageLink; 34 private TimePageLink pageLink;
  35 + private AlarmSearchStatus searchStatus;
35 private AlarmStatus status; 36 private AlarmStatus status;
  37 + private Boolean fetchOriginator;
36 38
37 } 39 }
1 -/* 1 +/**
2 * Copyright © 2016-2017 The Thingsboard Authors 2 * Copyright © 2016-2017 The Thingsboard Authors
3 * 3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,18 +14,10 @@ @@ -14,18 +14,10 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -/*@ngInject*/  
18 -export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, types, entityAliases, entityAliasesInfo, onEntityAliasesUpdate) { 17 +package org.thingsboard.server.common.data.alarm;
19 18
20 - var vm = this;  
21 - vm._mdPanelRef = mdPanelRef;  
22 - vm.entityAliases = entityAliases;  
23 - vm.entityAliasesInfo = entityAliasesInfo;  
24 - vm.onEntityAliasesUpdate = onEntityAliasesUpdate; 19 +public enum AlarmSearchStatus {
  20 +
  21 + ANY, ACTIVE, CLEARED, ACK, UNACK
25 22
26 - $scope.$watch('vm.entityAliases', function () {  
27 - if (onEntityAliasesUpdate) {  
28 - onEntityAliasesUpdate(vm.entityAliases);  
29 - }  
30 - }, true);  
31 } 23 }
@@ -30,4 +30,13 @@ public enum AlarmStatus { @@ -30,4 +30,13 @@ public enum AlarmStatus {
30 return this == CLEARED_ACK || this == CLEARED_UNACK; 30 return this == CLEARED_ACK || this == CLEARED_UNACK;
31 } 31 }
32 32
  33 + public AlarmSearchStatus getClearSearchStatus() {
  34 + return this.isCleared() ? AlarmSearchStatus.CLEARED : AlarmSearchStatus.ACTIVE;
  35 + }
  36 +
  37 + public AlarmSearchStatus getAckSearchStatus() {
  38 + return this.isAck() ? AlarmSearchStatus.ACK : AlarmSearchStatus.UNACK;
  39 + }
  40 +
  41 +
33 } 42 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.alarm.Alarm; 19 import org.thingsboard.server.common.data.alarm.Alarm;
  20 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
20 import org.thingsboard.server.common.data.alarm.AlarmQuery; 21 import org.thingsboard.server.common.data.alarm.AlarmQuery;
21 import org.thingsboard.server.common.data.id.EntityId; 22 import org.thingsboard.server.common.data.id.EntityId;
22 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
@@ -36,5 +37,5 @@ public interface AlarmDao extends Dao<Alarm> { @@ -36,5 +37,5 @@ public interface AlarmDao extends Dao<Alarm> {
36 37
37 Alarm save(Alarm alarm); 38 Alarm save(Alarm alarm);
38 39
39 - ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query); 40 + ListenableFuture<List<AlarmInfo>> findAlarms(AlarmQuery query);
40 } 41 }
@@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm; @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm;
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.alarm.Alarm; 19 import org.thingsboard.server.common.data.alarm.Alarm;
20 import org.thingsboard.server.common.data.alarm.AlarmId; 20 import org.thingsboard.server.common.data.alarm.AlarmId;
  21 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
21 import org.thingsboard.server.common.data.alarm.AlarmQuery; 22 import org.thingsboard.server.common.data.alarm.AlarmQuery;
22 import org.thingsboard.server.common.data.page.TimePageData; 23 import org.thingsboard.server.common.data.page.TimePageData;
23 24
@@ -34,6 +35,8 @@ public interface AlarmService { @@ -34,6 +35,8 @@ public interface AlarmService {
34 35
35 ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId); 36 ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
36 37
37 - ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query); 38 + ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId);
  39 +
  40 + ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query);
38 41
39 } 42 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
17 17
18 18
19 import com.google.common.base.Function; 19 import com.google.common.base.Function;
  20 +import com.google.common.util.concurrent.AsyncFunction;
20 import com.google.common.util.concurrent.Futures; 21 import com.google.common.util.concurrent.Futures;
21 import com.google.common.util.concurrent.ListenableFuture; 22 import com.google.common.util.concurrent.ListenableFuture;
22 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
@@ -24,15 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired; @@ -24,15 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
25 import org.springframework.util.StringUtils; 26 import org.springframework.util.StringUtils;
26 import org.thingsboard.server.common.data.Tenant; 27 import org.thingsboard.server.common.data.Tenant;
27 -import org.thingsboard.server.common.data.alarm.Alarm;  
28 -import org.thingsboard.server.common.data.alarm.AlarmId;  
29 -import org.thingsboard.server.common.data.alarm.AlarmQuery;  
30 -import org.thingsboard.server.common.data.alarm.AlarmStatus; 28 +import org.thingsboard.server.common.data.alarm.*;
31 import org.thingsboard.server.common.data.id.EntityId; 29 import org.thingsboard.server.common.data.id.EntityId;
32 import org.thingsboard.server.common.data.page.TimePageData; 30 import org.thingsboard.server.common.data.page.TimePageData;
33 import org.thingsboard.server.common.data.relation.EntityRelation; 31 import org.thingsboard.server.common.data.relation.EntityRelation;
34 import org.thingsboard.server.common.data.relation.RelationTypeGroup; 32 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
35 import org.thingsboard.server.dao.entity.AbstractEntityService; 33 import org.thingsboard.server.dao.entity.AbstractEntityService;
  34 +import org.thingsboard.server.dao.entity.EntityService;
36 import org.thingsboard.server.dao.exception.DataValidationException; 35 import org.thingsboard.server.dao.exception.DataValidationException;
37 import org.thingsboard.server.dao.relation.EntityRelationsQuery; 36 import org.thingsboard.server.dao.relation.EntityRelationsQuery;
38 import org.thingsboard.server.dao.relation.EntitySearchDirection; 37 import org.thingsboard.server.dao.relation.EntitySearchDirection;
@@ -44,6 +43,7 @@ import org.thingsboard.server.dao.tenant.TenantDao; @@ -44,6 +43,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
44 import javax.annotation.Nullable; 43 import javax.annotation.Nullable;
45 import javax.annotation.PostConstruct; 44 import javax.annotation.PostConstruct;
46 import javax.annotation.PreDestroy; 45 import javax.annotation.PreDestroy;
  46 +import java.util.ArrayList;
47 import java.util.List; 47 import java.util.List;
48 import java.util.concurrent.ExecutionException; 48 import java.util.concurrent.ExecutionException;
49 import java.util.concurrent.ExecutorService; 49 import java.util.concurrent.ExecutorService;
@@ -57,7 +57,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @@ -57,7 +57,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
57 public class BaseAlarmService extends AbstractEntityService implements AlarmService { 57 public class BaseAlarmService extends AbstractEntityService implements AlarmService {
58 58
59 public static final String ALARM_RELATION_PREFIX = "ALARM_"; 59 public static final String ALARM_RELATION_PREFIX = "ALARM_";
60 - public static final String ALARM_RELATION = "ALARM_ANY";  
61 60
62 @Autowired 61 @Autowired
63 private AlarmDao alarmDao; 62 private AlarmDao alarmDao;
@@ -68,6 +67,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -68,6 +67,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
68 @Autowired 67 @Autowired
69 private RelationService relationService; 68 private RelationService relationService;
70 69
  70 + @Autowired
  71 + private EntityService entityService;
  72 +
71 protected ExecutorService readResultsProcessingExecutor; 73 protected ExecutorService readResultsProcessingExecutor;
72 74
73 @PostConstruct 75 @PostConstruct
@@ -114,11 +116,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -114,11 +116,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
114 query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); 116 query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
115 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); 117 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
116 for (EntityId parentId : parentEntities) { 118 for (EntityId parentId : parentEntities) {
117 - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));  
118 - createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM)); 119 + createAlarmRelation(parentId, saved.getId(), saved.getStatus(), true);
119 } 120 }
120 - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));  
121 - createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM)); 121 + createAlarmRelation(alarm.getOriginator(), saved.getId(), saved.getStatus(), true);
122 return saved; 122 return saved;
123 } 123 }
124 124
@@ -197,12 +197,44 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -197,12 +197,44 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
197 } 197 }
198 198
199 @Override 199 @Override
200 - public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {  
201 - ListenableFuture<List<Alarm>> alarms = alarmDao.findAlarms(query);  
202 - return Futures.transform(alarms, new Function<List<Alarm>, TimePageData<Alarm>>() { 200 + public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId) {
  201 + log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId);
  202 + validateId(alarmId, "Incorrect alarmId " + alarmId);
  203 + return Futures.transform(alarmDao.findAlarmByIdAsync(alarmId.getId()),
  204 + (AsyncFunction<Alarm, AlarmInfo>) alarm1 -> {
  205 + AlarmInfo alarmInfo = new AlarmInfo(alarm1);
  206 + return Futures.transform(
  207 + entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function<String, AlarmInfo>)
  208 + originatorName -> {
  209 + alarmInfo.setOriginatorName(originatorName);
  210 + return alarmInfo;
  211 + }
  212 + );
  213 + });
  214 + }
  215 +
  216 + @Override
  217 + public ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query) {
  218 + ListenableFuture<List<AlarmInfo>> alarms = alarmDao.findAlarms(query);
  219 + if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
  220 + alarms = Futures.transform(alarms, (AsyncFunction<List<AlarmInfo>, List<AlarmInfo>>) input -> {
  221 + List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());
  222 + for (AlarmInfo alarmInfo : input) {
  223 + alarmFutures.add(Futures.transform(
  224 + entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function<String, AlarmInfo>)
  225 + originatorName -> {
  226 + alarmInfo.setOriginatorName(originatorName);
  227 + return alarmInfo;
  228 + }
  229 + ));
  230 + }
  231 + return Futures.successfulAsList(alarmFutures);
  232 + });
  233 + }
  234 + return Futures.transform(alarms, new Function<List<AlarmInfo>, TimePageData<AlarmInfo>>() {
203 @Nullable 235 @Nullable
204 @Override 236 @Override
205 - public TimePageData<Alarm> apply(@Nullable List<Alarm> alarms) { 237 + public TimePageData<AlarmInfo> apply(@Nullable List<AlarmInfo> alarms) {
206 return new TimePageData<>(alarms, query.getPageLink()); 238 return new TimePageData<>(alarms, query.getPageLink());
207 } 239 }
208 }); 240 });
@@ -243,17 +275,45 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -243,17 +275,45 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
243 query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE)); 275 query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
244 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList()); 276 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
245 for (EntityId parentId : parentEntities) { 277 for (EntityId parentId : parentEntities) {
246 - deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));  
247 - createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM));  
248 - }  
249 - deleteRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));  
250 - createRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM)); 278 + updateAlarmRelation(parentId, alarm.getId(), oldStatus, newStatus);
  279 + }
  280 + updateAlarmRelation(alarm.getOriginator(), alarm.getId(), oldStatus, newStatus);
251 } catch (ExecutionException | InterruptedException e) { 281 } catch (ExecutionException | InterruptedException e) {
252 log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus); 282 log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus);
253 throw new RuntimeException(e); 283 throw new RuntimeException(e);
254 } 284 }
255 } 285 }
256 286
  287 + private void createAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) {
  288 + try {
  289 + if (createAnyRelation) {
  290 + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM));
  291 + }
  292 + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM));
  293 + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM));
  294 + createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
  295 + } catch (ExecutionException | InterruptedException e) {
  296 + log.warn("[{}] Failed to create relation. Status: [{}]", alarmId, status);
  297 + throw new RuntimeException(e);
  298 + }
  299 + }
  300 +
  301 + private void deleteAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status) {
  302 + try {
  303 + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM));
  304 + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM));
  305 + deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
  306 + } catch (ExecutionException | InterruptedException e) {
  307 + log.warn("[{}] Failed to delete relation. Status: [{}]", alarmId, status);
  308 + throw new RuntimeException(e);
  309 + }
  310 + }
  311 +
  312 + private void updateAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) {
  313 + deleteAlarmRelation(entityId, alarmId, oldStatus);
  314 + createAlarmRelation(entityId, alarmId, newStatus, false);
  315 + }
  316 +
257 private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) { 317 private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) {
258 validateId(alarmId, "Alarm id should be specified!"); 318 validateId(alarmId, "Alarm id should be specified!");
259 ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId()); 319 ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
17 17
18 import com.datastax.driver.core.querybuilder.QueryBuilder; 18 import com.datastax.driver.core.querybuilder.QueryBuilder;
19 import com.datastax.driver.core.querybuilder.Select; 19 import com.datastax.driver.core.querybuilder.Select;
  20 +import com.google.common.base.Function;
20 import com.google.common.util.concurrent.AsyncFunction; 21 import com.google.common.util.concurrent.AsyncFunction;
21 import com.google.common.util.concurrent.Futures; 22 import com.google.common.util.concurrent.Futures;
22 import com.google.common.util.concurrent.ListenableFuture; 23 import com.google.common.util.concurrent.ListenableFuture;
@@ -26,7 +27,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -26,7 +27,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26 import org.springframework.stereotype.Component; 27 import org.springframework.stereotype.Component;
27 import org.thingsboard.server.common.data.EntityType; 28 import org.thingsboard.server.common.data.EntityType;
28 import org.thingsboard.server.common.data.alarm.Alarm; 29 import org.thingsboard.server.common.data.alarm.Alarm;
  30 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
29 import org.thingsboard.server.common.data.alarm.AlarmQuery; 31 import org.thingsboard.server.common.data.alarm.AlarmQuery;
  32 +import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
30 import org.thingsboard.server.common.data.id.EntityId; 33 import org.thingsboard.server.common.data.id.EntityId;
31 import org.thingsboard.server.common.data.id.TenantId; 34 import org.thingsboard.server.common.data.id.TenantId;
32 import org.thingsboard.server.common.data.relation.EntityRelation; 35 import org.thingsboard.server.common.data.relation.EntityRelation;
@@ -86,15 +89,25 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al @@ -86,15 +89,25 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
86 } 89 }
87 90
88 @Override 91 @Override
89 - public ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query) {  
90 - log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); 92 + public ListenableFuture<List<AlarmInfo>> findAlarms(AlarmQuery query) {
  93 + log.trace("Try to find alarms by entity [{}], searchStatus [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getSearchStatus(), query.getStatus(), query.getPageLink());
91 EntityId affectedEntity = query.getAffectedEntityId(); 94 EntityId affectedEntity = query.getAffectedEntityId();
92 - String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name(); 95 + String searchStatusName;
  96 + if (query.getSearchStatus() == null && query.getStatus() == null) {
  97 + searchStatusName = AlarmSearchStatus.ANY.name();
  98 + } else if (query.getSearchStatus() != null) {
  99 + searchStatusName = query.getSearchStatus().name();
  100 + } else {
  101 + searchStatusName = query.getStatus().name();
  102 + }
  103 + String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName;
93 ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); 104 ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
94 - return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Alarm>>) input -> {  
95 - List<ListenableFuture<Alarm>> alarmFutures = new ArrayList<>(input.size()); 105 + return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<AlarmInfo>>) input -> {
  106 + List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());
96 for (EntityRelation relation : input) { 107 for (EntityRelation relation : input) {
97 - alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId())); 108 + alarmFutures.add(Futures.transform(
  109 + findAlarmByIdAsync(relation.getTo().getId()), (Function<Alarm, AlarmInfo>)
  110 + alarm1 -> new AlarmInfo(alarm1)));
98 } 111 }
99 return Futures.successfulAsList(alarmFutures); 112 return Futures.successfulAsList(alarmFutures);
100 }); 113 });
@@ -179,9 +179,14 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati @@ -179,9 +179,14 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
179 eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()), 179 eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()),
180 eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType), 180 eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType),
181 eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())), 181 eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())),
182 - Arrays.asList(QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY),  
183 - QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY),  
184 - QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)), 182 + Arrays.asList(
  183 + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY) :
  184 + QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY),
  185 + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_PROPERTY) :
  186 + QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY),
  187 + pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TO_TYPE_PROPERTY) :
  188 + QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)
  189 + ),
185 pageLink, ModelConstants.RELATION_TO_ID_PROPERTY); 190 pageLink, ModelConstants.RELATION_TO_ID_PROPERTY);
186 return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs)); 191 return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs));
187 } 192 }
@@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService { @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
191 191
192 @Override 192 @Override
193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) { 193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
194 - log.trace("Executing findByQuery [{}][{}]", query); 194 + log.trace("Executing findByQuery [{}]", query);
195 RelationsSearchParameters params = query.getParameters(); 195 RelationsSearchParameters params = query.getParameters();
196 final List<EntityTypeFilter> filters = query.getFilters(); 196 final List<EntityTypeFilter> filters = query.getFilters();
197 if (filters == null || filters.isEmpty()) { 197 if (filters == null || filters.isEmpty()) {
@@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService { @@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
224 } 224 }
225 } 225 }
226 226
  227 + @Override
  228 + public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
  229 + log.trace("Executing findInfoByQuery [{}]", query);
  230 + ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
  231 + EntitySearchDirection direction = query.getParameters().getDirection();
  232 + ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
  233 + (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
  234 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  235 + relations1.stream().forEach(relation ->
  236 + futures.add(fetchRelationInfoAsync(relation,
  237 + relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
  238 + (EntityRelationInfo relationInfo, String entityName) -> {
  239 + if (direction == EntitySearchDirection.FROM) {
  240 + relationInfo.setToName(entityName);
  241 + } else {
  242 + relationInfo.setFromName(entityName);
  243 + }
  244 + }))
  245 + );
  246 + return Futures.successfulAsList(futures);
  247 + });
  248 + return relationsInfo;
  249 + }
  250 +
227 protected void validate(EntityRelation relation) { 251 protected void validate(EntityRelation relation) {
228 if (relation == null) { 252 if (relation == null) {
229 throw new DataValidationException("Relation type should be specified!"); 253 throw new DataValidationException("Relation type should be specified!");
@@ -52,6 +52,8 @@ public interface RelationService { @@ -52,6 +52,8 @@ public interface RelationService {
52 52
53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
54 54
  55 + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
  56 +
55 // TODO: This method may be useful for some validations in the future 57 // TODO: This method may be useful for some validations in the future
56 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); 58 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
57 59
@@ -22,10 +22,7 @@ import org.junit.Before; @@ -22,10 +22,7 @@ import org.junit.Before;
22 import org.junit.Test; 22 import org.junit.Test;
23 import org.thingsboard.server.common.data.EntityType; 23 import org.thingsboard.server.common.data.EntityType;
24 import org.thingsboard.server.common.data.Tenant; 24 import org.thingsboard.server.common.data.Tenant;
25 -import org.thingsboard.server.common.data.alarm.Alarm;  
26 -import org.thingsboard.server.common.data.alarm.AlarmQuery;  
27 -import org.thingsboard.server.common.data.alarm.AlarmSeverity;  
28 -import org.thingsboard.server.common.data.alarm.AlarmStatus; 25 +import org.thingsboard.server.common.data.alarm.*;
29 import org.thingsboard.server.common.data.id.AssetId; 26 import org.thingsboard.server.common.data.id.AssetId;
30 import org.thingsboard.server.common.data.id.DeviceId; 27 import org.thingsboard.server.common.data.id.DeviceId;
31 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
@@ -117,7 +114,7 @@ public class AlarmServiceTest extends AbstractServiceTest { @@ -117,7 +114,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
117 Alarm created = alarmService.createOrUpdateAlarm(alarm); 114 Alarm created = alarmService.createOrUpdateAlarm(alarm);
118 115
119 // Check child relation 116 // Check child relation
120 - TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder() 117 + TimePageData<AlarmInfo> alarms = alarmService.findAlarms(AlarmQuery.builder()
121 .affectedEntityId(childId) 118 .affectedEntityId(childId)
122 .status(AlarmStatus.ACTIVE_UNACK).pageLink( 119 .status(AlarmStatus.ACTIVE_UNACK).pageLink(
123 new TimePageLink(1, 0L, System.currentTimeMillis(), false) 120 new TimePageLink(1, 0L, System.currentTimeMillis(), false)
  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 'brace/ext/language_tools';
  17 +import 'brace/mode/json';
  18 +import 'brace/theme/github';
  19 +import beautify from 'js-beautify';
  20 +
  21 +import './alarm-details-dialog.scss';
  22 +
  23 +const js_beautify = beautify.js;
  24 +
  25 +/*@ngInject*/
  26 +export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) {
  27 +
  28 + var vm = this;
  29 +
  30 + vm.alarmId = alarmId;
  31 + vm.types = types;
  32 + vm.alarm = null;
  33 +
  34 + vm.alarmUpdated = false;
  35 +
  36 + showingCallback.onShowing = function(scope, element) {
  37 + updateEditorSize(element);
  38 + }
  39 +
  40 + vm.alarmDetailsOptions = {
  41 + useWrapMode: false,
  42 + mode: 'json',
  43 + showGutter: false,
  44 + showPrintMargin: false,
  45 + theme: 'github',
  46 + advanced: {
  47 + enableSnippets: false,
  48 + enableBasicAutocompletion: false,
  49 + enableLiveAutocompletion: false
  50 + },
  51 + onLoad: function (_ace) {
  52 + vm.editor = _ace;
  53 + }
  54 + };
  55 +
  56 + vm.close = close;
  57 + vm.acknowledge = acknowledge;
  58 + vm.clear = clear;
  59 +
  60 + loadAlarm();
  61 +
  62 + function updateEditorSize(element) {
  63 + var newWidth = 600;
  64 + var newHeight = 200;
  65 + angular.element('#tb-alarm-details', element).height(newHeight.toString() + "px")
  66 + .width(newWidth.toString() + "px");
  67 + vm.editor.resize();
  68 + }
  69 +
  70 + function loadAlarm() {
  71 + alarmService.getAlarmInfo(vm.alarmId).then(
  72 + function success(alarm) {
  73 + vm.alarm = alarm;
  74 + loadAlarmFields();
  75 + },
  76 + function fail() {
  77 + vm.alarm = null;
  78 + }
  79 + );
  80 + }
  81 +
  82 + function loadAlarmFields() {
  83 + vm.createdTime = $filter('date')(vm.alarm.createdTime, 'yyyy-MM-dd HH:mm:ss');
  84 + vm.startTime = null;
  85 + if (vm.alarm.startTs) {
  86 + vm.startTime = $filter('date')(vm.alarm.startTs, 'yyyy-MM-dd HH:mm:ss');
  87 + }
  88 + vm.endTime = null;
  89 + if (vm.alarm.endTs) {
  90 + vm.endTime = $filter('date')(vm.alarm.endTs, 'yyyy-MM-dd HH:mm:ss');
  91 + }
  92 + vm.ackTime = null;
  93 + if (vm.alarm.ackTs) {
  94 + vm.ackTime = $filter('date')(vm.alarm.ackTs, 'yyyy-MM-dd HH:mm:ss')
  95 + }
  96 + vm.clearTime = null;
  97 + if (vm.alarm.clearTs) {
  98 + vm.clearTime = $filter('date')(vm.alarm.clearTs, 'yyyy-MM-dd HH:mm:ss');
  99 + }
  100 +
  101 + vm.alarmSeverity = $translate.instant(types.alarmSeverity[vm.alarm.severity].name);
  102 +
  103 + vm.alarmStatus = $translate.instant('alarm.display-status.' + vm.alarm.status);
  104 +
  105 + vm.alarmDetails = null;
  106 + if (vm.alarm.details) {
  107 + vm.alarmDetails = angular.toJson(vm.alarm.details);
  108 + vm.alarmDetails = js_beautify(vm.alarmDetails, {indent_size: 4});
  109 + }
  110 + }
  111 +
  112 + function acknowledge () {
  113 + alarmService.ackAlarm(vm.alarmId).then(
  114 + function success() {
  115 + vm.alarmUpdated = true;
  116 + loadAlarm();
  117 + }
  118 + );
  119 + }
  120 +
  121 + function clear () {
  122 + alarmService.clearAlarm(vm.alarmId).then(
  123 + function success() {
  124 + vm.alarmUpdated = true;
  125 + loadAlarm();
  126 + }
  127 + );
  128 + }
  129 +
  130 + function close () {
  131 + $mdDialog.hide(vm.alarmUpdated ? vm.alarm : null);
  132 + }
  133 +
  134 +}
  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-alarm-details-panel {
  18 + margin-left: 15px;
  19 + border: 1px solid #C0C0C0;
  20 + height: 100%;
  21 + #tb-alarm-details {
  22 + min-width: 600px;
  23 + min-height: 200px;
  24 + width: 100%;
  25 + height: 100%;
  26 + }
  27 +}
  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="{{ 'alarm.alarm-details' | translate }}">
  19 + <md-toolbar>
  20 + <div class="md-toolbar-tools">
  21 + <h2 translate>alarm.alarm-details</h2>
  22 + <span flex></span>
  23 + <md-button class="md-icon-button" ng-click="vm.close()">
  24 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  25 + </md-button>
  26 + </div>
  27 + </md-toolbar>
  28 + <md-dialog-content>
  29 + <div class="md-dialog-content" layout="column">
  30 + <div layout="row">
  31 + <md-input-container class="md-block">
  32 + <label translate>alarm.created-time</label>
  33 + <input ng-model="vm.createdTime" readonly>
  34 + </md-input-container>
  35 + <md-input-container flex class="md-block">
  36 + <label translate>alarm.originator</label>
  37 + <input ng-model="vm.alarm.originatorName" readonly>
  38 + </md-input-container>
  39 + </div>
  40 + <div layout="row" ng-if="vm.startTime || vm.endTime">
  41 + <md-input-container ng-if="vm.startTime" flex class="md-block">
  42 + <label translate>alarm.start-time</label>
  43 + <input ng-model="vm.startTime" readonly>
  44 + </md-input-container>
  45 + <md-input-container ng-if="vm.endTime" flex class="md-block">
  46 + <label translate>alarm.end-time</label>
  47 + <input ng-model="vm.endTime" readonly>
  48 + </md-input-container>
  49 + <span flex ng-if="!vm.startTime || !vm.endTime"></span>
  50 + </div>
  51 + <div layout="row" ng-if="vm.ackTime || vm.clearTime">
  52 + <md-input-container ng-if="vm.ackTime" flex class="md-block">
  53 + <label translate>alarm.ack-time</label>
  54 + <input ng-model="vm.ackTime" readonly>
  55 + </md-input-container>
  56 + <md-input-container ng-if="vm.clearTime" flex class="md-block">
  57 + <label translate>alarm.clear-time</label>
  58 + <input ng-model="vm.clearTime" readonly>
  59 + </md-input-container>
  60 + <span flex ng-if="!vm.ackTime || !vm.clearTime"></span>
  61 + </div>
  62 + <div layout="row">
  63 + <md-input-container flex class="md-block">
  64 + <label translate>alarm.type</label>
  65 + <input ng-model="vm.alarm.type" readonly>
  66 + </md-input-container>
  67 + <md-input-container flex class="md-block">
  68 + <label translate>alarm.severity</label>
  69 + <input class="tb-severity" ng-class="vm.types.alarmSeverity[vm.alarm.severity].class"
  70 + ng-model="vm.alarmSeverity" readonly>
  71 + </md-input-container>
  72 + <md-input-container flex class="md-block">
  73 + <label translate>alarm.status</label>
  74 + <input ng-model="vm.alarmStatus" readonly>
  75 + </md-input-container>
  76 + </div>
  77 + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>alarm.details</div>
  78 + <div flex class="tb-alarm-details-panel" layout="column">
  79 + <div flex id="tb-alarm-details" readonly
  80 + ui-ace="vm.alarmDetailsOptions"
  81 + ng-model="vm.alarmDetails">
  82 + </div>
  83 + </div>
  84 + </div>
  85 + </md-dialog-content>
  86 + <md-dialog-actions layout="row">
  87 + <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||
  88 + vm.alarm.status==vm.types.alarmStatus.clearedUnack"
  89 + class="md-raised md-primary"
  90 + ng-disabled="loading"
  91 + ng-click="vm.acknowledge()"
  92 + style="margin-right:20px;">{{ 'alarm.acknowledge' |
  93 + translate }}
  94 + </md-button>
  95 + <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||
  96 + vm.alarm.status==vm.types.alarmStatus.activeUnack"
  97 + class="md-raised md-primary"
  98 + ng-disabled="loading"
  99 + ng-click="vm.clear()">{{ 'alarm.clear' |
  100 + translate }}
  101 + </md-button>
  102 + <span flex></span>
  103 + <md-button ng-disabled="loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |
  104 + translate }}
  105 + </md-button>
  106 + </md-dialog-actions>
  107 +</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 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import alarmHeaderTemplate from './alarm-header.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +/*@ngInject*/
  23 +export default function AlarmHeaderDirective($compile, $templateCache) {
  24 +
  25 + var linker = function (scope, element) {
  26 +
  27 + var template = $templateCache.get(alarmHeaderTemplate);
  28 + element.html(template);
  29 + $compile(element.contents())(scope);
  30 +
  31 + }
  32 +
  33 + return {
  34 + restrict: "A",
  35 + replace: false,
  36 + link: linker,
  37 + scope: false
  38 + };
  39 +}
ui/src/app/alarm/alarm-header.tpl.html renamed from ui/src/app/event/event-header-alarm.tpl.html
@@ -15,6 +15,9 @@ @@ -15,6 +15,9 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div translate class="tb-cell" flex="30">event.event-time</div>  
19 -<div translate class="tb-cell" flex="20">event.server</div>  
20 -<div translate class="tb-cell" flex="20">event.alarm</div> 18 +<div translate class="tb-cell" flex="30">alarm.created-time</div>
  19 +<div translate class="tb-cell" flex="15">alarm.originator</div>
  20 +<div translate class="tb-cell" flex="20">alarm.type</div>
  21 +<div translate class="tb-cell" flex="15">alarm.severity</div>
  22 +<div translate class="tb-cell" flex="20">alarm.status</div>
  23 +<div translate class="tb-cell" flex="15">alarm.details</div>
  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 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import alarmDetailsDialogTemplate from './alarm-details-dialog.tpl.html';
  19 +
  20 +import alarmRowTemplate from './alarm-row.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function AlarmRowDirective($compile, $templateCache, types, $mdDialog, $document) {
  26 +
  27 + var linker = function (scope, element, attrs) {
  28 +
  29 + var template = $templateCache.get(alarmRowTemplate);
  30 + element.html(template);
  31 +
  32 + scope.alarm = attrs.alarm;
  33 + scope.types = types;
  34 +
  35 + scope.showAlarmDetails = function($event) {
  36 + var onShowingCallback = {
  37 + onShowing: function(){}
  38 + }
  39 + $mdDialog.show({
  40 + controller: 'AlarmDetailsDialogController',
  41 + controllerAs: 'vm',
  42 + templateUrl: alarmDetailsDialogTemplate,
  43 + locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback},
  44 + parent: angular.element($document[0].body),
  45 + targetEvent: $event,
  46 + fullscreen: true,
  47 + skipHide: true,
  48 + onShowing: function(scope, element) {
  49 + onShowingCallback.onShowing(scope, element);
  50 + }
  51 + }).then(function (alarm) {
  52 + if (alarm) {
  53 + scope.alarm = alarm;
  54 + }
  55 + });
  56 + }
  57 +
  58 + $compile(element.contents())(scope);
  59 + }
  60 +
  61 + return {
  62 + restrict: "A",
  63 + replace: false,
  64 + link: linker,
  65 + scope: false
  66 + };
  67 +}
ui/src/app/alarm/alarm-row.tpl.html renamed from ui/src/app/event/event-row-alarm.tpl.html
@@ -15,14 +15,19 @@ @@ -15,14 +15,19 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>  
19 -<div class="tb-cell" flex="20">{{event.body.server}}</div>  
20 -<div class="tb-cell" flex="20">  
21 - <md-button ng-if="event.body.body" class="md-icon-button md-primary"  
22 - ng-click="showContent($event, event.body.body, 'event.alarm')" 18 +<div class="tb-cell" flex="30">{{alarm.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
  19 +<div class="tb-cell" flex="15">{{alarm.originatorName}}</div>
  20 +<div class="tb-cell" flex="20">{{alarm.type}}</div>
  21 +<div class="tb-cell tb-severity" flex="15" ng-class="types.alarmSeverity[alarm.severity].class">
  22 + {{ alarm ? (types.alarmSeverity[alarm.severity].name | translate) : '' }}
  23 +</div>
  24 +<div class="tb-cell" flex="20">{{ alarm ? (('alarm.display-status.' + alarm.status) | translate) : '' }}</div>
  25 +<div class="tb-cell" flex="15">
  26 + <md-button class="md-icon-button md-primary"
  27 + ng-click="showAlarmDetails($event)"
23 aria-label="{{ 'action.view' | translate }}"> 28 aria-label="{{ 'action.view' | translate }}">
24 <md-tooltip md-direction="top"> 29 <md-tooltip md-direction="top">
25 - {{ 'action.view' | translate }} 30 + {{ 'alarm.details' | translate }}
26 </md-tooltip> 31 </md-tooltip>
27 <md-icon aria-label="{{ 'action.view' | translate }}" 32 <md-icon aria-label="{{ 'action.view' | translate }}"
28 class="material-icons"> 33 class="material-icons">
  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 './alarm.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import alarmTableTemplate from './alarm-table.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +/*@ngInject*/
  25 +export default function AlarmTableDirective($compile, $templateCache, $rootScope, types, alarmService) {
  26 +
  27 + var linker = function (scope, element) {
  28 +
  29 + var template = $templateCache.get(alarmTableTemplate);
  30 +
  31 + element.html(template);
  32 +
  33 + scope.types = types;
  34 +
  35 + scope.alarmSearchStatus = types.alarmSearchStatus.any;
  36 +
  37 + var pageSize = 20;
  38 + var startTime = 0;
  39 + var endTime = 0;
  40 +
  41 + scope.timewindow = {
  42 + history: {
  43 + timewindowMs: 24 * 60 * 60 * 1000 // 1 day
  44 + }
  45 + }
  46 +
  47 + scope.topIndex = 0;
  48 +
  49 + scope.theAlarms = {
  50 + getItemAtIndex: function (index) {
  51 + if (index > scope.alarms.data.length) {
  52 + scope.theAlarms.fetchMoreItems_(index);
  53 + return null;
  54 + }
  55 + var item = scope.alarms.data[index];
  56 + if (item) {
  57 + item.indexNumber = index + 1;
  58 + }
  59 + return item;
  60 + },
  61 +
  62 + getLength: function () {
  63 + if (scope.alarms.hasNext) {
  64 + return scope.alarms.data.length + scope.alarms.nextPageLink.limit;
  65 + } else {
  66 + return scope.alarms.data.length;
  67 + }
  68 + },
  69 +
  70 + fetchMoreItems_: function () {
  71 + if (scope.alarms.hasNext && !scope.alarms.pending) {
  72 + if (scope.entityType && scope.entityId && scope.alarmSearchStatus) {
  73 + var promise = alarmService.getAlarms(scope.entityType, scope.entityId,
  74 + scope.alarms.nextPageLink, scope.alarmSearchStatus, null, true, false);
  75 + if (promise) {
  76 + scope.alarms.pending = true;
  77 + promise.then(
  78 + function success(alarms) {
  79 + scope.alarms.data = scope.alarms.data.concat(alarms.data);
  80 + scope.alarms.nextPageLink = alarms.nextPageLink;
  81 + scope.alarms.hasNext = alarms.hasNext;
  82 + if (scope.alarms.hasNext) {
  83 + scope.alarms.nextPageLink.limit = pageSize;
  84 + }
  85 + scope.alarms.pending = false;
  86 + },
  87 + function fail() {
  88 + scope.alarms.hasNext = false;
  89 + scope.alarms.pending = false;
  90 + });
  91 + } else {
  92 + scope.alarms.hasNext = false;
  93 + }
  94 + } else {
  95 + scope.alarms.hasNext = false;
  96 + }
  97 + }
  98 + }
  99 + };
  100 +
  101 + scope.$watch("entityId", function(newVal, prevVal) {
  102 + if (newVal && !angular.equals(newVal, prevVal)) {
  103 + resetFilter();
  104 + reload();
  105 + }
  106 + });
  107 +
  108 +
  109 +
  110 + function destroyWatchers() {
  111 + if (scope.alarmSearchStatusWatchHandle) {
  112 + scope.alarmSearchStatusWatchHandle();
  113 + scope.alarmSearchStatusWatchHandle = null;
  114 + }
  115 + if (scope.timewindowWatchHandle) {
  116 + scope.timewindowWatchHandle();
  117 + scope.timewindowWatchHandle = null;
  118 + }
  119 + }
  120 +
  121 + function initWatchers() {
  122 + scope.alarmSearchStatusWatchHandle = scope.$watch("alarmSearchStatus", function(newVal, prevVal) {
  123 + if (newVal && !angular.equals(newVal, prevVal)) {
  124 + reload();
  125 + }
  126 + });
  127 + scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) {
  128 + if (newVal && !angular.equals(newVal, prevVal)) {
  129 + reload();
  130 + }
  131 + }, true);
  132 + }
  133 +
  134 + function resetFilter() {
  135 + destroyWatchers();
  136 + scope.timewindow = {
  137 + history: {
  138 + timewindowMs: 24 * 60 * 60 * 1000 // 1 day
  139 + }
  140 + };
  141 + scope.alarmSearchStatus = types.alarmSearchStatus.any;
  142 + initWatchers();
  143 + }
  144 +
  145 + function updateTimeWindowRange () {
  146 + if (scope.timewindow.history.timewindowMs) {
  147 + var currentTime = (new Date).getTime();
  148 + startTime = currentTime - scope.timewindow.history.timewindowMs;
  149 + endTime = currentTime;
  150 + } else {
  151 + startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;
  152 + endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;
  153 + }
  154 + }
  155 +
  156 + function reload () {
  157 + scope.topIndex = 0;
  158 + scope.selected = [];
  159 + updateTimeWindowRange();
  160 + scope.alarms = {
  161 + data: [],
  162 + nextPageLink: {
  163 + limit: pageSize,
  164 + startTime: startTime,
  165 + endTime: endTime
  166 + },
  167 + hasNext: true,
  168 + pending: false
  169 + };
  170 + scope.theAlarms.getItemAtIndex(pageSize);
  171 + }
  172 +
  173 + scope.noData = function() {
  174 + return scope.alarms.data.length == 0 && !scope.alarms.hasNext;
  175 + }
  176 +
  177 + scope.hasData = function() {
  178 + return scope.alarms.data.length > 0;
  179 + }
  180 +
  181 + scope.loading = function() {
  182 + return $rootScope.loading;
  183 + }
  184 +
  185 + scope.hasScroll = function() {
  186 + var repeatContainer = scope.repeatContainer[0];
  187 + if (repeatContainer) {
  188 + var scrollElement = repeatContainer.children[0];
  189 + if (scrollElement) {
  190 + return scrollElement.scrollHeight > scrollElement.clientHeight;
  191 + }
  192 + }
  193 + return false;
  194 + }
  195 +
  196 + reload();
  197 +
  198 + initWatchers();
  199 +
  200 + $compile(element.contents())(scope);
  201 + }
  202 +
  203 + return {
  204 + restrict: "E",
  205 + link: linker,
  206 + scope: {
  207 + entityType: '=',
  208 + entityId: '='
  209 + }
  210 + };
  211 +}
  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" layout="column">
  19 + <section layout="row">
  20 + <md-input-container class="md-block" style="width: 200px;">
  21 + <label translate>alarm.alarm-status</label>
  22 + <md-select ng-model="alarmSearchStatus" ng-disabled="loading()">
  23 + <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
  24 + {{ ('alarm.search-status.' + searchStatus) | translate }}
  25 + </md-option>
  26 + </md-select>
  27 + </md-input-container>
  28 + <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
  29 + </section>
  30 + <div flex layout="column" class="tb-alarm-container md-whiteframe-z1">
  31 + <md-list flex layout="column" class="tb-alarm-table">
  32 + <md-list class="tb-row tb-header" layout="row" tb-alarm-header>
  33 + </md-list>
  34 + <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"
  35 + ng-show="loading()"></md-progress-linear>
  36 + <md-divider></md-divider>
  37 + <span translate layout-align="center center"
  38 + style="margin-top: 25px;"
  39 + class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span>
  40 + <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
  41 + <md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
  42 + <md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}">
  43 + </md-list>
  44 + <md-divider flex></md-divider>
  45 + </md-list-item>
  46 + </md-virtual-repeat-container>
  47 + </md-list>
  48 + </div>
  49 +</md-content>
  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-alarm-container {
  18 + overflow-x: auto;
  19 +}
  20 +
  21 +md-list.tb-alarm-table {
  22 + padding: 0px;
  23 + min-width: 700px;
  24 +
  25 + md-list-item {
  26 + padding: 0px;
  27 + }
  28 +
  29 + .tb-row {
  30 + height: 48px;
  31 + padding: 0px;
  32 + overflow: hidden;
  33 + }
  34 +
  35 + .tb-row:hover {
  36 + background-color: #EEEEEE;
  37 + }
  38 +
  39 + .tb-header:hover {
  40 + background: none;
  41 + }
  42 +
  43 + .tb-header {
  44 + .tb-cell {
  45 + color: rgba(0,0,0,.54);
  46 + font-size: 12px;
  47 + font-weight: 700;
  48 + white-space: nowrap;
  49 + background: none;
  50 + }
  51 + }
  52 +
  53 + .tb-cell {
  54 + padding: 0 24px;
  55 + margin: auto 0;
  56 + color: rgba(0,0,0,.87);
  57 + font-size: 13px;
  58 + vertical-align: middle;
  59 + text-align: left;
  60 + overflow: hidden;
  61 + .md-button {
  62 + padding: 0;
  63 + margin: 0;
  64 + }
  65 + }
  66 +
  67 + .tb-cell.tb-number {
  68 + text-align: right;
  69 + }
  70 +
  71 +}
  72 +
  73 +#tb-alarm-content {
  74 + min-width: 400px;
  75 + min-height: 50px;
  76 + width: 100%;
  77 + height: 100%;
  78 +}
  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 +import AlarmDetailsDialogController from './alarm-details-dialog.controller';
  18 +import AlarmHeaderDirective from './alarm-header.directive';
  19 +import AlarmRowDirective from './alarm-row.directive';
  20 +import AlarmTableDirective from './alarm-table.directive';
  21 +
  22 +export default angular.module('thingsboard.alarm', [])
  23 + .controller('AlarmDetailsDialogController', AlarmDetailsDialogController)
  24 + .directive('tbAlarmHeader', AlarmHeaderDirective)
  25 + .directive('tbAlarmRow', AlarmRowDirective)
  26 + .directive('tbAlarmTable', AlarmTableDirective)
  27 + .name;
@@ -21,6 +21,7 @@ export default angular.module('thingsboard.api.alarm', []) @@ -21,6 +21,7 @@ export default angular.module('thingsboard.api.alarm', [])
21 function AlarmService($http, $q, $interval, $filter) { 21 function AlarmService($http, $q, $interval, $filter) {
22 var service = { 22 var service = {
23 getAlarm: getAlarm, 23 getAlarm: getAlarm,
  24 + getAlarmInfo: getAlarmInfo,
24 saveAlarm: saveAlarm, 25 saveAlarm: saveAlarm,
25 ackAlarm: ackAlarm, 26 ackAlarm: ackAlarm,
26 clearAlarm: clearAlarm, 27 clearAlarm: clearAlarm,
@@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) { @@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) {
46 return deferred.promise; 47 return deferred.promise;
47 } 48 }
48 49
  50 + function getAlarmInfo(alarmId, ignoreErrors, config) {
  51 + var deferred = $q.defer();
  52 + var url = '/api/alarm/info/' + alarmId;
  53 + if (!config) {
  54 + config = {};
  55 + }
  56 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  57 + $http.get(url, config).then(function success(response) {
  58 + deferred.resolve(response.data);
  59 + }, function fail() {
  60 + deferred.reject();
  61 + });
  62 + return deferred.promise;
  63 + }
  64 +
49 function saveAlarm(alarm, ignoreErrors, config) { 65 function saveAlarm(alarm, ignoreErrors, config) {
50 var deferred = $q.defer(); 66 var deferred = $q.defer();
51 var url = '/api/alarm'; 67 var url = '/api/alarm';
@@ -91,7 +107,7 @@ function AlarmService($http, $q, $interval, $filter) { @@ -91,7 +107,7 @@ function AlarmService($http, $q, $interval, $filter) {
91 return deferred.promise; 107 return deferred.promise;
92 } 108 }
93 109
94 - function getAlarms(entityType, entityId, pageLink, alarmStatus, ascOrder, config) { 110 + function getAlarms(entityType, entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, ascOrder, config) {
95 var deferred = $q.defer(); 111 var deferred = $q.defer();
96 var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit; 112 var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit;
97 113
@@ -104,9 +120,15 @@ function AlarmService($http, $q, $interval, $filter) { @@ -104,9 +120,15 @@ function AlarmService($http, $q, $interval, $filter) {
104 if (angular.isDefined(pageLink.idOffset)) { 120 if (angular.isDefined(pageLink.idOffset)) {
105 url += '&offset=' + pageLink.idOffset; 121 url += '&offset=' + pageLink.idOffset;
106 } 122 }
  123 + if (alarmSearchStatus) {
  124 + url += '&searchStatus=' + alarmSearchStatus;
  125 + }
107 if (alarmStatus) { 126 if (alarmStatus) {
108 url += '&status=' + alarmStatus; 127 url += '&status=' + alarmStatus;
109 } 128 }
  129 + if (fetchOriginator) {
  130 + url += '&fetchOriginator=' + ((fetchOriginator===true) ? 'true' : 'false');
  131 + }
110 if (angular.isDefined(ascOrder) && ascOrder != null) { 132 if (angular.isDefined(ascOrder) && ascOrder != null) {
111 url += '&ascOrder=' + (ascOrder ? 'true' : 'false'); 133 url += '&ascOrder=' + (ascOrder ? 'true' : 'false');
112 } 134 }
@@ -121,7 +143,8 @@ function AlarmService($http, $q, $interval, $filter) { @@ -121,7 +143,8 @@ function AlarmService($http, $q, $interval, $filter) {
121 143
122 function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { 144 function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) {
123 getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, 145 getAlarms(alarmsQuery.entityType, alarmsQuery.entityId,
124 - pageLink, alarmsQuery.alarmStatus, false, {ignoreLoading: true}).then( 146 + pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus,
  147 + alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then(
125 function success(alarms) { 148 function success(alarms) {
126 if (!alarmsList) { 149 if (!alarmsList) {
127 alarmsList = []; 150 alarmsList = [];
@@ -171,7 +194,9 @@ function AlarmService($http, $q, $interval, $filter) { @@ -171,7 +194,9 @@ function AlarmService($http, $q, $interval, $filter) {
171 var alarmsQuery = { 194 var alarmsQuery = {
172 entityType: entityType, 195 entityType: entityType,
173 entityId: entityId, 196 entityId: entityId,
  197 + alarmSearchStatus: null,
174 alarmStatus: alarmStatus, 198 alarmStatus: alarmStatus,
  199 + fetchOriginator: false,
175 interval: interval, 200 interval: interval,
176 limit: limit, 201 limit: limit,
177 onAlarms: onAlarms 202 onAlarms: onAlarms
  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 +const varsRegex = /\$\{([^\}]*)\}/g;
  18 +
  19 +export default class AliasController {
  20 +
  21 + constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) {
  22 + this.$scope = $scope;
  23 + this.$q = $q;
  24 + this.$filter = $filter;
  25 + this.utils = utils;
  26 + this.types = types;
  27 + this.entityService = entityService;
  28 + this.stateController = stateController;
  29 + this.entityAliases = angular.copy(entityAliases);
  30 + this.resolvedAliases = {};
  31 + this.resolvedAliasesPromise = {};
  32 + this.resolvedAliasesToStateEntities = {};
  33 + }
  34 +
  35 + updateEntityAliases(newEntityAliases) {
  36 + var changedAliasIds = [];
  37 + for (var aliasId in newEntityAliases) {
  38 + var newEntityAlias = newEntityAliases[aliasId];
  39 + var prevEntityAlias = this.entityAliases[aliasId];
  40 + if (!angular.equals(newEntityAlias, prevEntityAlias)) {
  41 + changedAliasIds.push(aliasId);
  42 + this.setAliasUnresolved(aliasId);
  43 + }
  44 + }
  45 + for (aliasId in this.entityAliases) {
  46 + if (!newEntityAliases[aliasId]) {
  47 + changedAliasIds.push(aliasId);
  48 + this.setAliasUnresolved(aliasId);
  49 + }
  50 + }
  51 + this.entityAliases = angular.copy(newEntityAliases);
  52 + if (changedAliasIds.length) {
  53 + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
  54 + }
  55 + }
  56 +
  57 + dashboardStateChanged() {
  58 + var newEntityId = this.stateController.getStateParams().entityId;
  59 + var changedAliasIds = [];
  60 + for (var aliasId in this.resolvedAliasesToStateEntities) {
  61 + var prevEntityId = this.resolvedAliasesToStateEntities[aliasId];
  62 + if (!angular.equals(newEntityId, prevEntityId)) {
  63 + changedAliasIds.push(aliasId);
  64 + this.setAliasUnresolved(aliasId);
  65 + }
  66 + }
  67 + if (changedAliasIds.length) {
  68 + this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
  69 + }
  70 + }
  71 +
  72 + setAliasUnresolved(aliasId) {
  73 + delete this.resolvedAliases[aliasId];
  74 + delete this.resolvedAliasesPromise[aliasId];
  75 + delete this.resolvedAliasesToStateEntities[aliasId];
  76 + }
  77 +
  78 + getEntityAliases() {
  79 + return this.entityAliases;
  80 + }
  81 +
  82 + getAliasInfo(aliasId) {
  83 + var deferred = this.$q.defer();
  84 + var aliasInfo = this.resolvedAliases[aliasId];
  85 + if (aliasInfo) {
  86 + deferred.resolve(aliasInfo);
  87 + return deferred.promise;
  88 + } else if (this.resolvedAliasesPromise[aliasId]) {
  89 + return this.resolvedAliasesPromise[aliasId];
  90 + } else {
  91 + this.resolvedAliasesPromise[aliasId] = deferred.promise;
  92 + var aliasCtrl = this;
  93 + var entityAlias = this.entityAliases[aliasId];
  94 + if (entityAlias) {
  95 + this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
  96 + function success(aliasInfo) {
  97 + aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
  98 + if (aliasInfo.stateEntity) {
  99 + aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
  100 + aliasCtrl.stateController.getStateParams().entityId;
  101 + }
  102 + aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
  103 + deferred.resolve(aliasInfo);
  104 + },
  105 + function fail() {
  106 + deferred.reject();
  107 + }
  108 + );
  109 + } else {
  110 + deferred.reject();
  111 + }
  112 + return this.resolvedAliasesPromise[aliasId];
  113 + }
  114 + }
  115 +
  116 + resolveDatasource(datasource) {
  117 + var deferred = this.$q.defer();
  118 + if (datasource.type === this.types.datasourceType.entity) {
  119 + if (datasource.entityAliasId) {
  120 + this.getAliasInfo(datasource.entityAliasId).then(
  121 + function success(aliasInfo) {
  122 + datasource.aliasName = aliasInfo.alias;
  123 + if (aliasInfo.resolveMultiple) {
  124 + var newDatasource;
  125 + var resolvedEntities = aliasInfo.resolvedEntities;
  126 + if (resolvedEntities && resolvedEntities.length) {
  127 + var datasources = [];
  128 + for (var i=0;i<resolvedEntities.length;i++) {
  129 + var resolvedEntity = resolvedEntities[i];
  130 + newDatasource = angular.copy(datasource);
  131 + newDatasource.entityId = resolvedEntity.id;
  132 + newDatasource.entityType = resolvedEntity.entityType;
  133 + newDatasource.entityName = resolvedEntity.name;
  134 + newDatasource.name = resolvedEntity.name;
  135 + newDatasource.generated = i > 0 ? true : false;
  136 + datasources.push(newDatasource);
  137 + }
  138 + deferred.resolve(datasources);
  139 + } else {
  140 + if (aliasInfo.stateEntity) {
  141 + newDatasource = angular.copy(datasource);
  142 + newDatasource.unresolvedStateEntity = true;
  143 + deferred.resolve([newDatasource]);
  144 + } else {
  145 + deferred.reject();
  146 + }
  147 + }
  148 + } else {
  149 + var entity = aliasInfo.currentEntity;
  150 + if (entity) {
  151 + datasource.entityId = entity.id;
  152 + datasource.entityType = entity.entityType;
  153 + datasource.entityName = entity.name;
  154 + datasource.name = entity.name;
  155 + deferred.resolve([datasource]);
  156 + } else {
  157 + if (aliasInfo.stateEntity) {
  158 + datasource.unresolvedStateEntity = true;
  159 + deferred.resolve([datasource]);
  160 + } else {
  161 + deferred.reject();
  162 + }
  163 + }
  164 + }
  165 + },
  166 + function fail() {
  167 + deferred.reject();
  168 + }
  169 + );
  170 + } else { // entityId
  171 + datasource.aliasName = datasource.entityName;
  172 + datasource.name = datasource.entityName;
  173 + deferred.resolve([datasource]);
  174 + }
  175 + } else { // function
  176 + deferred.resolve([datasource]);
  177 + }
  178 + return deferred.promise;
  179 + }
  180 +
  181 + resolveDatasources(datasources) {
  182 +
  183 + function updateDataKeyLabel(dataKey, datasource) {
  184 + if (!dataKey.pattern) {
  185 + dataKey.pattern = angular.copy(dataKey.label);
  186 + }
  187 + var pattern = dataKey.pattern;
  188 + var label = dataKey.pattern;
  189 + var match = varsRegex.exec(pattern);
  190 + while (match !== null) {
  191 + var variable = match[0];
  192 + var variableName = match[1];
  193 + if (variableName === 'dsName') {
  194 + label = label.split(variable).join(datasource.name);
  195 + } else if (variableName === 'entityName') {
  196 + label = label.split(variable).join(datasource.entityName);
  197 + } else if (variableName === 'deviceName') {
  198 + label = label.split(variable).join(datasource.entityName);
  199 + } else if (variableName === 'aliasName') {
  200 + label = label.split(variable).join(datasource.aliasName);
  201 + }
  202 + match = varsRegex.exec(pattern);
  203 + }
  204 + dataKey.label = label;
  205 + }
  206 +
  207 + function updateDatasourceKeyLabels(datasource) {
  208 + for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
  209 + updateDataKeyLabel(datasource.dataKeys[dk], datasource);
  210 + }
  211 + }
  212 +
  213 + var deferred = this.$q.defer();
  214 + var newDatasources = angular.copy(datasources);
  215 + var datasorceResolveTasks = [];
  216 + var aliasCtrl = this;
  217 + newDatasources.forEach(function (datasource) {
  218 + var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource);
  219 + datasorceResolveTasks.push(resolveDatasourceTask);
  220 + });
  221 + this.$q.all(datasorceResolveTasks).then(
  222 + function success(datasourcesArrays) {
  223 + var datasources = [].concat.apply([], datasourcesArrays);
  224 + datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated');
  225 + var index = 0;
  226 + var functionIndex = 0;
  227 + datasources.forEach(function(datasource) {
  228 + if (datasource.type === aliasCtrl.types.datasourceType.function) {
  229 + var name;
  230 + if (datasource.name && datasource.name.length) {
  231 + name = datasource.name;
  232 + } else {
  233 + functionIndex++;
  234 + name = aliasCtrl.types.datasourceType.function;
  235 + if (functionIndex > 1) {
  236 + name += ' ' + functionIndex;
  237 + }
  238 + }
  239 + datasource.name = name;
  240 + datasource.aliasName = name;
  241 + datasource.entityName = name;
  242 + } else if (datasource.unresolvedStateEntity) {
  243 + datasource.name = "Unresolved";
  244 + datasource.entityName = "Unresolved";
  245 + }
  246 + datasource.dataKeys.forEach(function(dataKey) {
  247 + if (datasource.generated) {
  248 + dataKey._hash = Math.random();
  249 + dataKey.color = aliasCtrl.utils.getMaterialColor(index);
  250 + }
  251 + index++;
  252 + });
  253 + updateDatasourceKeyLabels(datasource);
  254 + });
  255 + deferred.resolve(datasources);
  256 + },
  257 + function fail() {
  258 + deferred.reject();
  259 + }
  260 + );
  261 + return deferred.promise;
  262 + }
  263 +
  264 + getInstantAliasInfo(aliasId) {
  265 + return this.resolvedAliases[aliasId];
  266 + }
  267 +
  268 + updateCurrentAliasEntity(aliasId, currentEntity) {
  269 + var aliasInfo = this.resolvedAliases[aliasId];
  270 + if (aliasInfo) {
  271 + var prevCurrentEntity = aliasInfo.currentEntity;
  272 + if (!angular.equals(currentEntity, prevCurrentEntity)) {
  273 + aliasInfo.currentEntity = currentEntity;
  274 + this.$scope.$broadcast('entityAliasesChanged', [aliasId]);
  275 + }
  276 + }
  277 + }
  278 +
  279 +}
@@ -30,7 +30,8 @@ function EntityRelationService($http, $q) { @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
30 findByTo: findByTo, 30 findByTo: findByTo,
31 findInfoByTo: findInfoByTo, 31 findInfoByTo: findInfoByTo,
32 findByToAndType: findByToAndType, 32 findByToAndType: findByToAndType,
33 - findByQuery: findByQuery 33 + findByQuery: findByQuery,
  34 + findInfoByQuery: findInfoByQuery
34 } 35 }
35 36
36 return service; 37 return service;
@@ -159,4 +160,15 @@ function EntityRelationService($http, $q) { @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
159 return deferred.promise; 160 return deferred.promise;
160 } 161 }
161 162
  163 + function findInfoByQuery(query) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/relations/info';
  166 + $http.post(url, query).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
  170 + });
  171 + return deferred.promise;
  172 + }
  173 +
162 } 174 }
@@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
27 getEntity: getEntity, 27 getEntity: getEntity,
28 getEntities: getEntities, 28 getEntities: getEntities,
29 getEntitiesByNameFilter: getEntitiesByNameFilter, 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30 - processEntityAliases: processEntityAliases,  
31 - getEntityKeys: getEntityKeys, 30 + resolveAlias: resolveAlias,
  31 + resolveAliasFilter: resolveAliasFilter,
32 checkEntityAlias: checkEntityAlias, 32 checkEntityAlias: checkEntityAlias,
33 - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo, 33 + filterAliasByEntityTypes: filterAliasByEntityTypes,
  34 + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
  35 + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
  36 + getEntityKeys: getEntityKeys,
  37 + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
34 getRelatedEntities: getRelatedEntities, 38 getRelatedEntities: getRelatedEntities,
35 saveRelatedEntity: saveRelatedEntity, 39 saveRelatedEntity: saveRelatedEntity,
36 getRelatedEntity: getRelatedEntity, 40 getRelatedEntity: getRelatedEntity,
@@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
173 return deferred.promise; 177 return deferred.promise;
174 } 178 }
175 179
  180 + function getSingleTenantByPageLinkPromise(pageLink) {
  181 + var user = userService.getCurrentUser();
  182 + var tenantId = user.tenantId;
  183 + var deferred = $q.defer();
  184 + tenantService.getTenant(tenantId).then(
  185 + function success(tenant) {
  186 + var tenantName = tenant.name;
  187 + var result = {
  188 + data: [],
  189 + nextPageLink: pageLink,
  190 + hasNext: false
  191 + };
  192 + if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
  193 + result.data.push(tenant);
  194 + }
  195 + deferred.resolve(result);
  196 + },
  197 + function fail() {
  198 + deferred.reject();
  199 + }
  200 + );
  201 + return deferred.promise;
  202 + }
  203 +
  204 + function getSingleCustomerByPageLinkPromise(pageLink) {
  205 + var user = userService.getCurrentUser();
  206 + var customerId = user.customerId;
  207 + var deferred = $q.defer();
  208 + customerService.getCustomer(customerId).then(
  209 + function success(customer) {
  210 + var customerName = customer.name;
  211 + var result = {
  212 + data: [],
  213 + nextPageLink: pageLink,
  214 + hasNext: false
  215 + };
  216 + if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
  217 + result.data.push(customer);
  218 + }
  219 + deferred.resolve(result);
  220 + },
  221 + function fail() {
  222 + deferred.reject();
  223 + }
  224 + );
  225 + return deferred.promise;
  226 + }
  227 +
176 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) { 228 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
177 var promise; 229 var promise;
178 var user = userService.getCurrentUser(); 230 var user = userService.getCurrentUser();
@@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
193 } 245 }
194 break; 246 break;
195 case types.entityType.tenant: 247 case types.entityType.tenant:
196 - promise = tenantService.getTenants(pageLink); 248 + if (user.authority === 'TENANT_ADMIN') {
  249 + promise = getSingleTenantByPageLinkPromise(pageLink);
  250 + } else {
  251 + promise = tenantService.getTenants(pageLink);
  252 + }
197 break; 253 break;
198 case types.entityType.customer: 254 case types.entityType.customer:
199 - promise = customerService.getCustomers(pageLink); 255 + if (user.authority === 'CUSTOMER_USER') {
  256 + promise = getSingleCustomerByPageLinkPromise(pageLink);
  257 + } else {
  258 + promise = customerService.getCustomers(pageLink);
  259 + }
200 break; 260 break;
201 case types.entityType.rule: 261 case types.entityType.rule:
202 promise = ruleService.getAllRules(pageLink); 262 promise = ruleService.getAllRules(pageLink);
@@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
221 return promise; 281 return promise;
222 } 282 }
223 283
224 - function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {  
225 - var deferred = $q.defer();  
226 - var pageLink = {limit: limit, textSearch: entityNameFilter}; 284 + function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) {
227 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType); 285 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
228 if (promise) { 286 if (promise) {
229 promise.then( 287 promise.then(
230 function success(result) { 288 function success(result) {
231 - if (result.data && result.data.length > 0) {  
232 - deferred.resolve(result.data); 289 + data = data.concat(result.data);
  290 + if (result.hasNext) {
  291 + pageLink = result.nextPageLink;
  292 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
233 } else { 293 } else {
234 - deferred.resolve(null); 294 + if (data && data.length > 0) {
  295 + deferred.resolve(data);
  296 + } else {
  297 + deferred.resolve(null);
  298 + }
235 } 299 }
236 }, 300 },
237 function fail() { 301 function fail() {
@@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
241 } else { 305 } else {
242 deferred.resolve(null); 306 deferred.resolve(null);
243 } 307 }
  308 + }
  309 +
  310 + function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
  311 + var deferred = $q.defer();
  312 + var pageLink = {limit: limit, textSearch: entityNameFilter};
  313 + if (limit == -1) { // all
  314 + var data = [];
  315 + pageLink.limit = 100;
  316 + getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
  317 + } else {
  318 + var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
  319 + if (promise) {
  320 + promise.then(
  321 + function success(result) {
  322 + if (result.data && result.data.length > 0) {
  323 + deferred.resolve(result.data);
  324 + } else {
  325 + deferred.resolve(null);
  326 + }
  327 + },
  328 + function fail() {
  329 + deferred.resolve(null);
  330 + }
  331 + );
  332 + } else {
  333 + deferred.resolve(null);
  334 + }
  335 + }
244 return deferred.promise; 336 return deferred.promise;
245 } 337 }
246 338
247 - function entityToEntityInfo(entityType, entity) {  
248 - return { name: entity.name, entityType: entityType, id: entity.id.id }; 339 + function entityToEntityInfo(entity) {
  340 + return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
249 } 341 }
250 342
251 - function entitiesToEntitiesInfo(entityType, entities) { 343 + function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
  344 + var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
  345 + var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
  346 + return {
  347 + name: name,
  348 + entityType: entityId.entityType,
  349 + id: entityId.id
  350 + };
  351 + }
  352 +
  353 + function entitiesToEntitiesInfo(entities) {
252 var entitiesInfo = []; 354 var entitiesInfo = [];
253 for (var d = 0; d < entities.length; d++) { 355 for (var d = 0; d < entities.length; d++) {
254 - entitiesInfo.push(entityToEntityInfo(entityType, entities[d])); 356 + entitiesInfo.push(entityToEntityInfo(entities[d]));
255 } 357 }
256 return entitiesInfo; 358 return entitiesInfo;
257 } 359 }
258 360
259 - function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) {  
260 - if (index < aliasIds.length) {  
261 - var aliasId = aliasIds[index];  
262 - var entityAlias = entityAliases[aliasId];  
263 - var alias = entityAlias.alias;  
264 - var entityFilter = entityAlias.entityFilter;  
265 - if (entityFilter.useFilter) {  
266 - var entityNameFilter = entityFilter.entityNameFilter;  
267 - getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then(  
268 - function(entities) {  
269 - if (entities && entities != null) {  
270 - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};  
271 - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;  
272 - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);  
273 - index++;  
274 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 361 + function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
  362 + var entitiesInfo = [];
  363 + for (var d = 0; d < entityRelations.length; d++) {
  364 + entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
  365 + }
  366 + return entitiesInfo;
  367 + }
  368 +
  369 +
  370 + function resolveAlias(entityAlias, stateParams) {
  371 + var deferred = $q.defer();
  372 + var filter = entityAlias.filter;
  373 + resolveAliasFilter(filter, stateParams, -1).then(
  374 + function (result) {
  375 + var aliasInfo = {
  376 + alias: entityAlias.alias,
  377 + stateEntity: result.stateEntity,
  378 + resolveMultiple: filter.resolveMultiple
  379 + };
  380 + aliasInfo.resolvedEntities = result.entities;
  381 + aliasInfo.currentEntity = null;
  382 + if (aliasInfo.resolvedEntities.length) {
  383 + aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
  384 + }
  385 + deferred.resolve(aliasInfo);
  386 + },
  387 + function fail() {
  388 + deferred.reject();
  389 + }
  390 + );
  391 + return deferred.promise;
  392 + }
  393 +
  394 + function resolveAliasFilter(filter, stateParams, maxItems) {
  395 + var deferred = $q.defer();
  396 + var result = {
  397 + entities: [],
  398 + stateEntity: false
  399 + };
  400 + switch (filter.type) {
  401 + case types.aliasFilterType.entityList.value:
  402 + getEntities(filter.entityType, filter.entityList).then(
  403 + function success(entities) {
  404 + if (entities && entities.length) {
  405 + result.entities = entitiesToEntitiesInfo(entities);
  406 + deferred.resolve(result);
275 } else { 407 } else {
276 - if (!resolution.error) {  
277 - resolution.error = 'dashboard.invalid-aliases-config';  
278 - }  
279 - index++;  
280 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 408 + deferred.reject();
281 } 409 }
282 - });  
283 - } else {  
284 - var entityList = entityFilter.entityList;  
285 - getEntities(entityAlias.entityType, entityList).then( 410 + },
  411 + function fail() {
  412 + deferred.reject();
  413 + }
  414 + );
  415 + break;
  416 + case types.aliasFilterType.entityName.value:
  417 + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
286 function success(entities) { 418 function success(entities) {
287 - if (entities && entities.length > 0) {  
288 - var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};  
289 - resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;  
290 - resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);  
291 - index++;  
292 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 419 + if (entities && entities.length) {
  420 + result.entities = entitiesToEntitiesInfo(entities);
  421 + deferred.resolve(result);
293 } else { 422 } else {
294 - if (!resolution.error) {  
295 - resolution.error = 'dashboard.invalid-aliases-config';  
296 - }  
297 - index++;  
298 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 423 + deferred.reject();
  424 + }
  425 + },
  426 + function fail() {
  427 + deferred.reject();
  428 + }
  429 + );
  430 + break;
  431 + case types.aliasFilterType.stateEntity.value:
  432 + result.stateEntity = true;
  433 + if (stateParams && stateParams.entityId) {
  434 + getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
  435 + function success(entity) {
  436 + result.entities = entitiesToEntitiesInfo([entity]);
  437 + deferred.resolve(result);
  438 + },
  439 + function fail() {
  440 + deferred.reject();
  441 + }
  442 + );
  443 + } else {
  444 + deferred.resolve(result);
  445 + }
  446 + break;
  447 + case types.aliasFilterType.assetType.value:
  448 + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
  449 + function success(entities) {
  450 + if (entities && entities.length) {
  451 + result.entities = entitiesToEntitiesInfo(entities);
  452 + deferred.resolve(result);
  453 + } else {
  454 + deferred.reject();
299 } 455 }
300 }, 456 },
301 function fail() { 457 function fail() {
302 - if (!resolution.error) {  
303 - resolution.error = 'dashboard.invalid-aliases-config'; 458 + deferred.reject();
  459 + }
  460 + );
  461 + break;
  462 + case types.aliasFilterType.deviceType.value:
  463 + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
  464 + function success(entities) {
  465 + if (entities && entities.length) {
  466 + result.entities = entitiesToEntitiesInfo(entities);
  467 + deferred.resolve(result);
  468 + } else {
  469 + deferred.reject();
304 } 470 }
305 - index++;  
306 - processEntityAlias(index, aliasIds, entityAliases, resolution, deferred); 471 + },
  472 + function fail() {
  473 + deferred.reject();
307 } 474 }
308 ); 475 );
  476 + break;
  477 + case types.aliasFilterType.relationsQuery.value:
  478 + result.stateEntity = filter.rootStateEntity;
  479 + var rootEntityType;
  480 + var rootEntityId;
  481 + if (result.stateEntity && stateParams && stateParams.entityId) {
  482 + rootEntityType = stateParams.entityId.entityType;
  483 + rootEntityId = stateParams.entityId.id;
  484 + } else if (!result.stateEntity) {
  485 + rootEntityType = filter.rootEntity.entityType;
  486 + rootEntityId = filter.rootEntity.id;
  487 + }
  488 + if (rootEntityType && rootEntityId) {
  489 + var searchQuery = {
  490 + parameters: {
  491 + rootId: rootEntityId,
  492 + rootType: rootEntityType,
  493 + direction: filter.direction
  494 + },
  495 + filters: filter.filters
  496 + };
  497 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  498 + entityRelationService.findInfoByQuery(searchQuery).then(
  499 + function success(allRelations) {
  500 + if (allRelations && allRelations.length) {
  501 + if (angular.isDefined(maxItems) && maxItems > 0) {
  502 + var limit = Math.min(allRelations.length, maxItems);
  503 + allRelations.length = limit;
  504 + }
  505 + result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
  506 + deferred.resolve(result);
  507 + } else {
  508 + deferred.reject();
  509 + }
  510 + },
  511 + function fail() {
  512 + deferred.reject();
  513 + }
  514 + );
  515 + } else {
  516 + deferred.resolve(result);
  517 + }
  518 + break;
  519 + case types.aliasFilterType.assetSearchQuery.value:
  520 + case types.aliasFilterType.deviceSearchQuery.value:
  521 + result.stateEntity = filter.rootStateEntity;
  522 + if (result.stateEntity && stateParams && stateParams.entityId) {
  523 + rootEntityType = stateParams.entityId.entityType;
  524 + rootEntityId = stateParams.entityId.id;
  525 + } else if (!result.stateEntity) {
  526 + rootEntityType = filter.rootEntity.entityType;
  527 + rootEntityId = filter.rootEntity.id;
  528 + }
  529 + if (rootEntityType && rootEntityId) {
  530 + searchQuery = {
  531 + parameters: {
  532 + rootId: rootEntityId,
  533 + rootType: rootEntityType,
  534 + direction: filter.direction
  535 + },
  536 + relationType: filter.relationType
  537 + };
  538 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
  539 + var findByQueryPromise;
  540 + if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
  541 + searchQuery.assetTypes = filter.assetTypes;
  542 + findByQueryPromise = assetService.findByQuery(searchQuery, false);
  543 + } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
  544 + searchQuery.deviceTypes = filter.deviceTypes;
  545 + findByQueryPromise = deviceService.findByQuery(searchQuery, false);
  546 + }
  547 + findByQueryPromise.then(
  548 + function success(entities) {
  549 + if (entities && entities.length) {
  550 + if (angular.isDefined(maxItems) && maxItems > 0) {
  551 + var limit = Math.min(entities.length, maxItems);
  552 + entities.length = limit;
  553 + }
  554 + result.entities = entitiesToEntitiesInfo(entities);
  555 + deferred.resolve(result);
  556 + } else {
  557 + deferred.reject();
  558 + }
  559 + },
  560 + function fail() {
  561 + deferred.reject();
  562 + }
  563 + );
  564 + } else {
  565 + deferred.resolve(result);
  566 + }
  567 + break;
  568 + }
  569 + return deferred.promise;
  570 + }
  571 +
  572 + function filterAliasByEntityTypes(entityAlias, entityTypes) {
  573 + var filter = entityAlias.filter;
  574 + if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) {
  575 + switch (filter.type) {
  576 + case types.aliasFilterType.entityList.value:
  577 + return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  578 + case types.aliasFilterType.entityName.value:
  579 + return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
  580 + case types.aliasFilterType.stateEntity.value:
  581 + return true;
  582 + case types.aliasFilterType.assetType.value:
  583 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  584 + case types.aliasFilterType.deviceType.value:
  585 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
  586 + case types.aliasFilterType.relationsQuery.value:
  587 + if (filter.filters && filter.filters.length) {
  588 + var match = false;
  589 + for (var f=0;f<filter.filters.length;f++) {
  590 + var relationFilter = filter.filters[f];
  591 + if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
  592 + for (var et=0;et<relationFilter.entityTypes.length;et++) {
  593 + if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
  594 + match = true;
  595 + break;
  596 + }
  597 + }
  598 + } else {
  599 + match = true;
  600 + break;
  601 + }
  602 + }
  603 + return match;
  604 + } else {
  605 + return true;
  606 + }
  607 + case types.aliasFilterType.assetSearchQuery.value:
  608 + return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
  609 + case types.aliasFilterType.deviceSearchQuery.value:
  610 + return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
309 } 611 }
310 - } else {  
311 - deferred.resolve(resolution);  
312 } 612 }
  613 + return false;
313 } 614 }
314 615
315 - function processEntityAliases(entityAliases) {  
316 - var deferred = $q.defer();  
317 - var resolution = {  
318 - aliasesInfo: {  
319 - entityAliases: {},  
320 - entityAliasesInfo: {} 616 + function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
  617 + switch (aliasFilterType) {
  618 + case types.aliasFilterType.entityList.value:
  619 + return true;
  620 + case types.aliasFilterType.entityName.value:
  621 + return true;
  622 + case types.aliasFilterType.stateEntity.value:
  623 + return true;
  624 + case types.aliasFilterType.assetType.value:
  625 + return entityType === types.entityType.asset;
  626 + case types.aliasFilterType.deviceType.value:
  627 + return entityType === types.entityType.device;
  628 + case types.aliasFilterType.relationsQuery.value:
  629 + return true;
  630 + case types.aliasFilterType.assetSearchQuery.value:
  631 + return entityType === types.entityType.asset;
  632 + case types.aliasFilterType.deviceSearchQuery.value:
  633 + return entityType === types.entityType.device;
  634 + }
  635 + return false;
  636 + }
  637 +
  638 + function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) {
  639 + if (!entityTypes || !entityTypes.length) {
  640 + return true;
  641 + }
  642 + var valid = false;
  643 + entityTypes.forEach(function(entityType) {
  644 + valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType);
  645 + });
  646 + return valid;
  647 + }
  648 +
  649 + function getAliasFilterTypesByEntityTypes(entityTypes) {
  650 + var allAliasFilterTypes = types.aliasFilterType;
  651 + if (!entityTypes || !entityTypes.length) {
  652 + return allAliasFilterTypes;
  653 + }
  654 + var result = {};
  655 + for (var type in allAliasFilterTypes) {
  656 + var aliasFilterType = allAliasFilterTypes[type];
  657 + if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) {
  658 + result[type] = aliasFilterType;
321 } 659 }
322 - };  
323 - var aliasIds = [];  
324 - if (entityAliases) {  
325 - for (var aliasId in entityAliases) {  
326 - aliasIds.push(aliasId); 660 + }
  661 + return result;
  662 + }
  663 +
  664 + function prepareAllowedEntityTypesList(allowedEntityTypes) {
  665 + var authority = userService.getAuthority();
  666 + var entityTypes = {};
  667 + switch(authority) {
  668 + case 'SYS_ADMIN':
  669 + entityTypes.tenant = types.entityType.tenant;
  670 + entityTypes.rule = types.entityType.rule;
  671 + entityTypes.plugin = types.entityType.plugin;
  672 + break;
  673 + case 'TENANT_ADMIN':
  674 + entityTypes.device = types.entityType.device;
  675 + entityTypes.asset = types.entityType.asset;
  676 + entityTypes.tenant = types.entityType.tenant;
  677 + entityTypes.customer = types.entityType.customer;
  678 + entityTypes.rule = types.entityType.rule;
  679 + entityTypes.plugin = types.entityType.plugin;
  680 + entityTypes.dashboard = types.entityType.dashboard;
  681 + break;
  682 + case 'CUSTOMER_USER':
  683 + entityTypes.device = types.entityType.device;
  684 + entityTypes.asset = types.entityType.asset;
  685 + entityTypes.customer = types.entityType.customer;
  686 + entityTypes.dashboard = types.entityType.dashboard;
  687 + break;
  688 + }
  689 +
  690 + if (allowedEntityTypes) {
  691 + for (var entityType in entityTypes) {
  692 + if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
  693 + delete entityTypes[entityType];
  694 + }
327 } 695 }
328 } 696 }
329 - processEntityAlias(0, aliasIds, entityAliases, resolution, deferred); 697 + return entityTypes;
  698 + }
  699 +
  700 +
  701 + function checkEntityAlias(entityAlias) {
  702 + var deferred = $q.defer();
  703 + resolveAliasFilter(entityAlias.filter, null, 1).then(
  704 + function success(result) {
  705 + if (result.stateEntity) {
  706 + deferred.resolve(true);
  707 + } else {
  708 + var entities = result.entities;
  709 + if (entities && entities.length) {
  710 + deferred.resolve(true);
  711 + } else {
  712 + deferred.resolve(false);
  713 + }
  714 + }
  715 + },
  716 + function fail() {
  717 + deferred.resolve(false);
  718 + }
  719 + );
330 return deferred.promise; 720 return deferred.promise;
331 } 721 }
332 722
@@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
354 } 744 }
355 } 745 }
356 deferred.resolve(result); 746 deferred.resolve(result);
357 - }, function fail(response) {  
358 - deferred.reject(response.data); 747 + }, function fail() {
  748 + deferred.reject();
359 }); 749 });
360 return deferred.promise; 750 return deferred.promise;
361 } 751 }
362 752
363 - function checkEntityAlias(entityAlias) {  
364 - var deferred = $q.defer();  
365 - var entityType = entityAlias.entityType;  
366 - var entityFilter = entityAlias.entityFilter;  
367 - var promise;  
368 - if (entityFilter.useFilter) {  
369 - var entityNameFilter = entityFilter.entityNameFilter;  
370 - promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1);  
371 - } else {  
372 - var entityList = entityFilter.entityList;  
373 - promise = getEntities(entityType, entityList);  
374 - }  
375 - promise.then(  
376 - function success(entities) {  
377 - if (entities && entities.length > 0) {  
378 - deferred.resolve(true);  
379 - } else {  
380 - deferred.resolve(false);  
381 - }  
382 - },  
383 - function fail() {  
384 - deferred.resolve(false);  
385 - }  
386 - );  
387 - return deferred.promise;  
388 - }  
389 -  
390 - function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) { 753 + function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) {
391 var deferred = $q.defer(); 754 var deferred = $q.defer();
392 var datasources = []; 755 var datasources = [];
393 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred); 756 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
@@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
822 } 1185 }
823 } 1186 }
824 1187
825 -}  
  1188 +}
@@ -39,6 +39,9 @@ export default class Subscription { @@ -39,6 +39,9 @@ export default class Subscription {
39 this.cafs = {}; 39 this.cafs = {};
40 this.registrations = []; 40 this.registrations = [];
41 41
  42 + var subscription = this;
  43 + var deferred = this.ctx.$q.defer();
  44 +
42 if (this.type === this.ctx.types.widgetType.rpc.value) { 45 if (this.type === this.ctx.types.widgetType.rpc.value) {
43 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){}; 46 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
44 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){}; 47 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
@@ -56,7 +59,11 @@ export default class Subscription { @@ -56,7 +59,11 @@ export default class Subscription {
56 this.rpcEnabled = false; 59 this.rpcEnabled = false;
57 this.executingRpcRequest = false; 60 this.executingRpcRequest = false;
58 this.executingPromises = []; 61 this.executingPromises = [];
59 - this.initRpc(); 62 + this.initRpc().then(
  63 + function() {
  64 + deferred.resolve(subscription);
  65 + }
  66 + );
60 } else { 67 } else {
61 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){}; 68 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
62 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){}; 69 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
@@ -66,6 +73,15 @@ export default class Subscription { @@ -66,6 +73,15 @@ export default class Subscription {
66 73
67 this.datasources = this.ctx.utils.validateDatasources(options.datasources); 74 this.datasources = this.ctx.utils.validateDatasources(options.datasources);
68 this.datasourceListeners = []; 75 this.datasourceListeners = [];
  76 +
  77 + /*
  78 + * data = array of datasourceData
  79 + * datasourceData = {
  80 + * tbDatasource,
  81 + * dataKey, { name, config }
  82 + * data = array of [time, value]
  83 + * }
  84 + */
69 this.data = []; 85 this.data = [];
70 this.hiddenData = []; 86 this.hiddenData = [];
71 this.originalTimewindow = null; 87 this.originalTimewindow = null;
@@ -103,11 +119,41 @@ export default class Subscription { @@ -103,11 +119,41 @@ export default class Subscription {
103 this.legendConfig.showMax === true || 119 this.legendConfig.showMax === true ||
104 this.legendConfig.showAvg === true || 120 this.legendConfig.showAvg === true ||
105 this.legendConfig.showTotal === true); 121 this.legendConfig.showTotal === true);
106 - this.initDataSubscription(); 122 + this.initDataSubscription().then(
  123 + function success() {
  124 + deferred.resolve(subscription);
  125 + },
  126 + function fail() {
  127 + deferred.reject();
  128 + }
  129 + );
107 } 130 }
  131 +
  132 + return deferred.promise;
108 } 133 }
109 134
110 initDataSubscription() { 135 initDataSubscription() {
  136 + var deferred = this.ctx.$q.defer();
  137 + if (!this.ctx.aliasController) {
  138 + this.configureData();
  139 + deferred.resolve();
  140 + } else {
  141 + var subscription = this;
  142 + this.ctx.aliasController.resolveDatasources(this.datasources).then(
  143 + function success(datasources) {
  144 + subscription.datasources = datasources;
  145 + subscription.configureData();
  146 + deferred.resolve();
  147 + },
  148 + function fail() {
  149 + deferred.reject();
  150 + }
  151 + );
  152 + }
  153 + return deferred.promise;
  154 + }
  155 +
  156 + configureData() {
111 var dataIndex = 0; 157 var dataIndex = 0;
112 for (var i = 0; i < this.datasources.length; i++) { 158 for (var i = 0; i < this.datasources.length; i++) {
113 var datasource = this.datasources[i]; 159 var datasource = this.datasources[i];
@@ -199,21 +245,46 @@ export default class Subscription { @@ -199,21 +245,46 @@ export default class Subscription {
199 } 245 }
200 246
201 initRpc() { 247 initRpc() {
  248 + var deferred = this.ctx.$q.defer();
202 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { 249 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
203 this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; 250 this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
204 - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {  
205 - this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId; 251 + var subscription = this;
  252 + this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then(
  253 + function success(aliasInfo) {
  254 + if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
  255 + subscription.targetDeviceId = aliasInfo.currentEntity.id;
  256 + if (subscription.targetDeviceId) {
  257 + subscription.rpcEnabled = true;
  258 + } else {
  259 + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
  260 + }
  261 + subscription.callbacks.rpcStateChanged(subscription);
  262 + deferred.resolve();
  263 + } else {
  264 + subscription.rpcEnabled = false;
  265 + subscription.callbacks.rpcStateChanged(subscription);
  266 + deferred.resolve();
  267 + }
  268 + },
  269 + function fail () {
  270 + subscription.rpcEnabled = false;
  271 + subscription.callbacks.rpcStateChanged(subscription);
  272 + deferred.resolve();
  273 + }
  274 + );
  275 + } else {
  276 + if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
  277 + this.targetDeviceId = this.targetDeviceIds[0];
206 } 278 }
207 - } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {  
208 - this.targetDeviceId = this.targetDeviceIds[0];  
209 - }  
210 -  
211 - if (this.targetDeviceId) {  
212 - this.rpcEnabled = true;  
213 - } else {  
214 - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; 279 + if (this.targetDeviceId) {
  280 + this.rpcEnabled = true;
  281 + } else {
  282 + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
  283 + }
  284 + this.callbacks.rpcStateChanged(this);
  285 + deferred.resolve();
215 } 286 }
216 - this.callbacks.rpcStateChanged(this); 287 + return deferred.promise;
217 } 288 }
218 289
219 clearRpcError() { 290 clearRpcError() {
@@ -319,11 +390,11 @@ export default class Subscription { @@ -319,11 +390,11 @@ export default class Subscription {
319 this.onDataUpdated(); 390 this.onDataUpdated();
320 } 391 }
321 392
322 - onAliasesChanged() { 393 + onAliasesChanged(aliasIds) {
323 if (this.type === this.ctx.types.widgetType.rpc.value) { 394 if (this.type === this.ctx.types.widgetType.rpc.value) {
324 - this.checkRpcTarget(); 395 + return this.checkRpcTarget(aliasIds);
325 } else { 396 } else {
326 - this.checkSubscriptions(); 397 + return this.checkSubscriptions(aliasIds);
327 } 398 }
328 } 399 }
329 400
@@ -481,39 +552,6 @@ export default class Subscription { @@ -481,39 +552,6 @@ export default class Subscription {
481 var datasource = this.datasources[i]; 552 var datasource = this.datasources[i];
482 if (angular.isFunction(datasource)) 553 if (angular.isFunction(datasource))
483 continue; 554 continue;
484 - var entityId = null;  
485 - var entityType = null;  
486 - if (datasource.type === this.ctx.types.datasourceType.entity) {  
487 - var aliasName = null;  
488 - var entityName = null;  
489 - if (datasource.entityId) {  
490 - entityId = datasource.entityId;  
491 - entityType = datasource.entityType;  
492 - datasource.name = datasource.entityName;  
493 - aliasName = datasource.entityName;  
494 - entityName = datasource.entityName;  
495 - } else if (datasource.entityAliasId) {  
496 - if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) {  
497 - entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId;  
498 - entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType;  
499 - datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;  
500 - aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;  
501 - entityName = '';  
502 - var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId];  
503 - for (var d = 0; d < entitiesInfo.length; d++) {  
504 - if (entitiesInfo[d].id === entityId) {  
505 - entityName = entitiesInfo[d].name;  
506 - break;  
507 - }  
508 - }  
509 - }  
510 - }  
511 - } else {  
512 - datasource.name = datasource.name || this.ctx.types.datasourceType.function;  
513 - }  
514 - for (var dk = 0; dk < datasource.dataKeys.length; dk++) {  
515 - updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName);  
516 - }  
517 555
518 var subscription = this; 556 var subscription = this;
519 557
@@ -521,8 +559,8 @@ export default class Subscription { @@ -521,8 +559,8 @@ export default class Subscription {
521 subscriptionType: this.type, 559 subscriptionType: this.type,
522 subscriptionTimewindow: this.subscriptionTimewindow, 560 subscriptionTimewindow: this.subscriptionTimewindow,
523 datasource: datasource, 561 datasource: datasource,
524 - entityType: entityType,  
525 - entityId: entityId, 562 + entityType: datasource.entityType,
  563 + entityId: datasource.entityId,
526 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { 564 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
527 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); 565 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
528 }, 566 },
@@ -544,6 +582,10 @@ export default class Subscription { @@ -544,6 +582,10 @@ export default class Subscription {
544 582
545 this.datasourceListeners.push(listener); 583 this.datasourceListeners.push(listener);
546 this.ctx.datasourceService.subscribeToDatasource(listener); 584 this.ctx.datasourceService.subscribeToDatasource(listener);
  585 + if (datasource.unresolvedStateEntity) {
  586 + this.notifyDataLoaded();
  587 + this.onDataUpdated();
  588 + }
547 } 589 }
548 } 590 }
549 591
@@ -557,48 +599,26 @@ export default class Subscription { @@ -557,48 +599,26 @@ export default class Subscription {
557 } 599 }
558 } 600 }
559 601
560 - checkRpcTarget() {  
561 - var deviceId = null;  
562 - if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {  
563 - deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;  
564 - }  
565 - if (!angular.equals(deviceId, this.targetDeviceId)) {  
566 - this.targetDeviceId = deviceId;  
567 - if (this.targetDeviceId) {  
568 - this.rpcEnabled = true;  
569 - } else {  
570 - this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;  
571 - }  
572 - this.callbacks.rpcStateChanged(this); 602 + checkRpcTarget(aliasIds) {
  603 + if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {
  604 + return true;
  605 + } else {
  606 + return false;
573 } 607 }
574 } 608 }
575 609
576 - checkSubscriptions() { 610 + checkSubscriptions(aliasIds) {
577 var subscriptionsChanged = false; 611 var subscriptionsChanged = false;
578 for (var i = 0; i < this.datasourceListeners.length; i++) { 612 for (var i = 0; i < this.datasourceListeners.length; i++) {
579 var listener = this.datasourceListeners[i]; 613 var listener = this.datasourceListeners[i];
580 - var entityId = null;  
581 - var entityType = null;  
582 - var aliasName = null;  
583 - if (listener.datasource.type === this.ctx.types.datasourceType.entity) {  
584 - if (listener.datasource.entityAliasId &&  
585 - this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) {  
586 - entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId;  
587 - entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType;  
588 - aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias;  
589 - }  
590 - if (!angular.equals(entityId, listener.entityId) ||  
591 - !angular.equals(entityType, listener.entityType) ||  
592 - !angular.equals(aliasName, listener.datasource.name)) { 614 + if (listener.datasource.entityAliasId) {
  615 + if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
593 subscriptionsChanged = true; 616 subscriptionsChanged = true;
594 break; 617 break;
595 } 618 }
596 } 619 }
597 } 620 }
598 - if (subscriptionsChanged) {  
599 - this.unsubscribe();  
600 - this.subscribe();  
601 - } 621 + return subscriptionsChanged;
602 } 622 }
603 623
604 destroy() { 624 destroy() {
@@ -617,29 +637,6 @@ export default class Subscription { @@ -617,29 +637,6 @@ export default class Subscription {
617 637
618 } 638 }
619 639
620 -const varsRegex = /\$\{([^\}]*)\}/g;  
621 -  
622 -function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) {  
623 - var pattern = dataKey.pattern;  
624 - var label = dataKey.pattern;  
625 - var match = varsRegex.exec(pattern);  
626 - while (match !== null) {  
627 - var variable = match[0];  
628 - var variableName = match[1];  
629 - if (variableName === 'dsName') {  
630 - label = label.split(variable).join(dsName);  
631 - } else if (variableName === 'entityName') {  
632 - label = label.split(variable).join(entityName);  
633 - } else if (variableName === 'deviceName') {  
634 - label = label.split(variable).join(entityName);  
635 - } else if (variableName === 'aliasName') {  
636 - label = label.split(variable).join(aliasName);  
637 - }  
638 - match = varsRegex.exec(pattern);  
639 - }  
640 - dataKey.label = label;  
641 -}  
642 -  
643 function calculateMin(data) { 640 function calculateMin(data) {
644 if (data.length > 0) { 641 if (data.length > 0) {
645 var result = Number(data[0][1]); 642 var result = Number(data[0][1]);
@@ -48,11 +48,16 @@ @@ -48,11 +48,16 @@
48 disable-attribute-scope-selection="true"> 48 disable-attribute-scope-selection="true">
49 </tb-attribute-table> 49 </tb-attribute-table>
50 </md-tab> 50 </md-tab>
  51 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
  52 + <tb-alarm-table flex entity-type="vm.types.entityType.asset"
  53 + entity-id="vm.grid.operatingItem().id.id">
  54 + </tb-alarm-table>
  55 + </md-tab>
51 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}"> 56 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}">
52 <tb-event-table flex entity-type="vm.types.entityType.asset" 57 <tb-event-table flex entity-type="vm.types.entityType.asset"
53 entity-id="vm.grid.operatingItem().id.id" 58 entity-id="vm.grid.operatingItem().id.id"
54 tenant-id="vm.grid.operatingItem().tenantId.id" 59 tenant-id="vm.grid.operatingItem().tenantId.id"
55 - default-event-type="{{vm.types.eventType.alarm.value}}"> 60 + default-event-type="{{vm.types.eventType.error.value}}">
56 </tb-event-table> 61 </tb-event-table>
57 </md-tab> 62 </md-tab>
58 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> 63 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 import uiRouter from 'angular-ui-router'; 16 import uiRouter from 'angular-ui-router';
17 import thingsboardGrid from '../components/grid.directive'; 17 import thingsboardGrid from '../components/grid.directive';
18 -import thingsboardEvent from '../event';  
19 import thingsboardApiUser from '../api/user.service'; 18 import thingsboardApiUser from '../api/user.service';
20 import thingsboardApiAsset from '../api/asset.service'; 19 import thingsboardApiAsset from '../api/asset.service';
21 import thingsboardApiCustomer from '../api/customer.service'; 20 import thingsboardApiCustomer from '../api/customer.service';
@@ -29,7 +28,6 @@ import AssetDirective from './asset.directive'; @@ -29,7 +28,6 @@ import AssetDirective from './asset.directive';
29 export default angular.module('thingsboard.asset', [ 28 export default angular.module('thingsboard.asset', [
30 uiRouter, 29 uiRouter,
31 thingsboardGrid, 30 thingsboardGrid,
32 - thingsboardEvent,  
33 thingsboardApiUser, 31 thingsboardApiUser,
34 thingsboardApiAsset, 32 thingsboardApiAsset,
35 thingsboardApiCustomer 33 thingsboardApiCustomer
@@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) { @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
23 23
24 var service = { 24 var service = {
25 validateAndUpdateDashboard: validateAndUpdateDashboard, 25 validateAndUpdateDashboard: validateAndUpdateDashboard,
  26 + validateAndUpdateWidget: validateAndUpdateWidget,
26 getRootStateId: getRootStateId, 27 getRootStateId: getRootStateId,
27 createSingleWidgetDashboard: createSingleWidgetDashboard, 28 createSingleWidgetDashboard: createSingleWidgetDashboard,
  29 + createSingleEntityFilter: createSingleEntityFilter,
28 getStateLayoutsData: getStateLayoutsData, 30 getStateLayoutsData: getStateLayoutsData,
29 createDefaultState: createDefaultState, 31 createDefaultState: createDefaultState,
30 createDefaultLayoutData: createDefaultLayoutData, 32 createDefaultLayoutData: createDefaultLayoutData,
@@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) { @@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
39 41
40 return service; 42 return service;
41 43
42 - function validateAndUpdateEntityAliases(configuration) { 44 + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
  45 + var aliasId, entityAlias;
43 if (angular.isUndefined(configuration.entityAliases)) { 46 if (angular.isUndefined(configuration.entityAliases)) {
44 configuration.entityAliases = {}; 47 configuration.entityAliases = {};
45 if (configuration.deviceAliases) { 48 if (configuration.deviceAliases) {
46 var deviceAliases = configuration.deviceAliases; 49 var deviceAliases = configuration.deviceAliases;
47 - for (var aliasId in deviceAliases) { 50 + for (aliasId in deviceAliases) {
48 var deviceAlias = deviceAliases[aliasId]; 51 var deviceAlias = deviceAliases[aliasId];
49 - var alias = deviceAlias.alias;  
50 - var entityFilter = {  
51 - useFilter: false,  
52 - entityNameFilter: '',  
53 - entityList: []  
54 - }  
55 - if (deviceAlias.deviceFilter) {  
56 - entityFilter.useFilter = deviceAlias.deviceFilter.useFilter;  
57 - entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;  
58 - entityFilter.entityList = deviceAlias.deviceFilter.deviceList;  
59 - } else if (deviceAlias.deviceId) {  
60 - entityFilter.entityList = [deviceAlias.deviceId];  
61 - }  
62 - var entityAlias = {  
63 - id: aliasId,  
64 - alias: alias,  
65 - entityType: types.entityType.device,  
66 - entityFilter: entityFilter  
67 - };  
68 - configuration.entityAliases[aliasId] = entityAlias; 52 + entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId);
  53 + configuration.entityAliases[entityAlias.id] = entityAlias;
69 } 54 }
70 delete configuration.deviceAliases; 55 delete configuration.deviceAliases;
71 } 56 }
  57 + } else {
  58 + var entityAliases = configuration.entityAliases;
  59 + for (aliasId in entityAliases) {
  60 + entityAlias = entityAliases[aliasId];
  61 + entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId);
  62 + if (aliasId != entityAlias.id) {
  63 + delete entityAliases[aliasId];
  64 + }
  65 + entityAliases[entityAlias.id] = entityAlias;
  66 + }
72 } 67 }
73 return configuration; 68 return configuration;
74 } 69 }
75 70
  71 + function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) {
  72 + if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) {
  73 + var newAliasId = utils.guid();
  74 + var aliasDatasources = datasourcesByAliasId[aliasId];
  75 + if (aliasDatasources) {
  76 + aliasDatasources.forEach(
  77 + function(datasource) {
  78 + datasource.entityAliasId = newAliasId;
  79 + }
  80 + );
  81 + }
  82 + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
  83 + if (targetDeviceAliasIdsList) {
  84 + targetDeviceAliasIdsList.forEach(
  85 + function(targetDeviceAliasIds) {
  86 + targetDeviceAliasIds[0] = newAliasId;
  87 + }
  88 + );
  89 + }
  90 + return newAliasId;
  91 + } else {
  92 + return aliasId;
  93 + }
  94 + }
  95 +
  96 + function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) {
  97 + aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
  98 + var alias = deviceAlias.alias;
  99 + var entityAlias = {
  100 + id: aliasId,
  101 + alias: alias,
  102 + filter: {
  103 + type: null,
  104 + entityType: types.entityType.device,
  105 + resolveMultiple: false
  106 + },
  107 + }
  108 + if (deviceAlias.deviceFilter) {
  109 + entityAlias.filter.type =
  110 + deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
  111 + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
  112 + entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
  113 + } else {
  114 + entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
  115 + }
  116 + } else {
  117 + entityAlias.filter.type = types.aliasFilterType.entityList.value;
  118 + entityAlias.filter.entityList = [deviceAlias.deviceId];
  119 + }
  120 + return entityAlias;
  121 + }
  122 +
  123 + function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) {
  124 + entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
  125 + if (!entityAlias.filter) {
  126 + entityAlias.filter = {
  127 + type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
  128 + entityType: entityAlias.entityType,
  129 + resolveMultiple: false
  130 + }
  131 + if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
  132 + entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
  133 + } else {
  134 + entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
  135 + }
  136 + delete entityAlias.entityType;
  137 + delete entityAlias.entityFilter;
  138 + }
  139 + return entityAlias;
  140 + }
  141 +
76 function validateAndUpdateWidget(widget) { 142 function validateAndUpdateWidget(widget) {
77 if (!widget.config) { 143 if (!widget.config) {
78 widget.config = {}; 144 widget.config = {};
@@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) { @@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
166 states[firstStateId].root = true; 232 states[firstStateId].root = true;
167 } 233 }
168 } 234 }
169 - dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration); 235 +
  236 + var datasourcesByAliasId = {};
  237 + var targetDevicesByAliasId = {};
  238 + for (var widgetId in dashboard.configuration.widgets) {
  239 + widget = dashboard.configuration.widgets[widgetId];
  240 + widget.config.datasources.forEach(function (datasource) {
  241 + if (datasource.entityAliasId) {
  242 + var aliasId = datasource.entityAliasId;
  243 + var aliasDatasources = datasourcesByAliasId[aliasId];
  244 + if (!aliasDatasources) {
  245 + aliasDatasources = [];
  246 + datasourcesByAliasId[aliasId] = aliasDatasources;
  247 + }
  248 + aliasDatasources.push(datasource);
  249 + }
  250 + });
  251 + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) {
  252 + var aliasId = widget.config.targetDeviceAliasIds[0];
  253 + var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
  254 + if (!targetDeviceAliasIdsList) {
  255 + targetDeviceAliasIdsList = [];
  256 + targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList;
  257 + }
  258 + targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds);
  259 + }
  260 + }
  261 +
  262 + dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId);
170 263
171 if (angular.isUndefined(dashboard.configuration.timewindow)) { 264 if (angular.isUndefined(dashboard.configuration.timewindow)) {
172 dashboard.configuration.timewindow = timeService.defaultTimewindow(); 265 dashboard.configuration.timewindow = timeService.defaultTimewindow();
@@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) { @@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
243 return dashboard; 336 return dashboard;
244 } 337 }
245 338
  339 + function createSingleEntityFilter(entityType, entityId) {
  340 + return {
  341 + type: types.aliasFilterType.entityList.value,
  342 + entityList: [entityId],
  343 + entityType: entityType,
  344 + resolveMultiple: false
  345 + };
  346 + }
  347 +
246 function getStateLayoutsData(dashboard, targetState) { 348 function getStateLayoutsData(dashboard, targetState) {
247 var dashboardConfiguration = dashboard.configuration; 349 var dashboardConfiguration = dashboard.configuration;
248 var states = dashboardConfiguration.states; 350 var states = dashboardConfiguration.states;
@@ -65,6 +65,69 @@ export default angular.module('thingsboard.types', []) @@ -65,6 +65,69 @@ export default angular.module('thingsboard.types', [])
65 clearedUnack: "CLEARED_UNACK", 65 clearedUnack: "CLEARED_UNACK",
66 clearedAck: "CLEARED_ACK" 66 clearedAck: "CLEARED_ACK"
67 }, 67 },
  68 + alarmSearchStatus: {
  69 + any: "ANY",
  70 + active: "ACTIVE",
  71 + cleared: "CLEARED",
  72 + ack: "ACK",
  73 + unack: "UNACK"
  74 + },
  75 + alarmSeverity: {
  76 + "CRITICAL": {
  77 + name: "alarm.severity-critical",
  78 + class: "tb-critical"
  79 + },
  80 + "MAJOR": {
  81 + name: "alarm.severity-major",
  82 + class: "tb-major"
  83 + },
  84 + "MINOR": {
  85 + name: "alarm.severity-minor",
  86 + class: "tb-minor"
  87 + },
  88 + "WARNING": {
  89 + name: "alarm.severity-warning",
  90 + class: "tb-warning"
  91 + },
  92 + "INDETERMINATE": {
  93 + name: "alarm.severity-indeterminate",
  94 + class: "tb-indeterminate"
  95 + }
  96 + },
  97 + aliasFilterType: {
  98 + entityList: {
  99 + value: 'entityList',
  100 + name: 'alias.filter-type-entity-list'
  101 + },
  102 + entityName: {
  103 + value: 'entityName',
  104 + name: 'alias.filter-type-entity-name'
  105 + },
  106 + stateEntity: {
  107 + value: 'stateEntity',
  108 + name: 'alias.filter-type-state-entity'
  109 + },
  110 + assetType: {
  111 + value: 'assetType',
  112 + name: 'alias.filter-type-asset-type'
  113 + },
  114 + deviceType: {
  115 + value: 'deviceType',
  116 + name: 'alias.filter-type-device-type'
  117 + },
  118 + relationsQuery: {
  119 + value: 'relationsQuery',
  120 + name: 'alias.filter-type-relations-query'
  121 + },
  122 + assetSearchQuery: {
  123 + value: 'assetSearchQuery',
  124 + name: 'alias.filter-type-asset-search-query'
  125 + },
  126 + deviceSearchQuery: {
  127 + value: 'deviceSearchQuery',
  128 + name: 'alias.filter-type-device-search-query'
  129 + }
  130 + },
68 position: { 131 position: {
69 top: { 132 top: {
70 value: "top", 133 value: "top",
@@ -109,6 +172,62 @@ export default angular.module('thingsboard.types', []) @@ -109,6 +172,62 @@ export default angular.module('thingsboard.types', [])
109 dashboard: "DASHBOARD", 172 dashboard: "DASHBOARD",
110 alarm: "ALARM" 173 alarm: "ALARM"
111 }, 174 },
  175 + entityTypeTranslations: {
  176 + "DEVICE": {
  177 + type: 'entity.type-device',
  178 + typePlural: 'entity.type-devices',
  179 + list: 'entity.list-of-devices',
  180 + nameStartsWith: 'entity.device-name-starts-with'
  181 + },
  182 + "ASSET": {
  183 + type: 'entity.type-asset',
  184 + typePlural: 'entity.type-assets',
  185 + list: 'entity.list-of-assets',
  186 + nameStartsWith: 'entity.asset-name-starts-with'
  187 + },
  188 + "RULE": {
  189 + type: 'entity.type-rule',
  190 + typePlural: 'entity.type-rules',
  191 + list: 'entity.list-of-rules',
  192 + nameStartsWith: 'entity.rule-name-starts-with'
  193 + },
  194 + "PLUGIN": {
  195 + type: 'entity.type-plugin',
  196 + typePlural: 'entity.type-plugins',
  197 + list: 'entity.list-of-plugins',
  198 + nameStartsWith: 'entity.plugin-name-starts-with'
  199 + },
  200 + "TENANT": {
  201 + type: 'entity.type-tenant',
  202 + typePlural: 'entity.type-tenants',
  203 + list: 'entity.list-of-tenants',
  204 + nameStartsWith: 'entity.tenant-name-starts-with'
  205 + },
  206 + "CUSTOMER": {
  207 + type: 'entity.type-customer',
  208 + typePlural: 'entity.type-customers',
  209 + list: 'entity.list-of-customers',
  210 + nameStartsWith: 'entity.customer-name-starts-with'
  211 + },
  212 + "USER": {
  213 + type: 'entity.type-user',
  214 + typePlural: 'entity.type-users',
  215 + list: 'entity.list-of-users',
  216 + nameStartsWith: 'entity.user-name-starts-with'
  217 + },
  218 + "DASHBOARD": {
  219 + type: 'entity.type-dashboard',
  220 + typePlural: 'entity.type-dashboards',
  221 + list: 'entity.list-of-dashboards',
  222 + nameStartsWith: 'entity.dashboard-name-starts-with'
  223 + },
  224 + "ALARM": {
  225 + type: 'entity.type-alarm',
  226 + typePlural: 'entity.type-alarms',
  227 + list: 'entity.list-of-alarms',
  228 + nameStartsWith: 'entity.alarm-name-starts-with'
  229 + }
  230 + },
112 entitySearchDirection: { 231 entitySearchDirection: {
113 from: "FROM", 232 from: "FROM",
114 to: "TO" 233 to: "TO"
@@ -118,10 +237,6 @@ export default angular.module('thingsboard.types', []) @@ -118,10 +237,6 @@ export default angular.module('thingsboard.types', [])
118 manages: "Manages" 237 manages: "Manages"
119 }, 238 },
120 eventType: { 239 eventType: {
121 - alarm: {  
122 - value: "ALARM",  
123 - name: "event.type-alarm"  
124 - },  
125 error: { 240 error: {
126 value: "ERROR", 241 value: "ERROR",
127 name: "event.type-error" 242 name: "event.type-error"
@@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 filterSearchTextEntities: filterSearchTextEntities, 107 filterSearchTextEntities: filterSearchTextEntities,
108 guid: guid, 108 guid: guid,
  109 + cleanCopy: cleanCopy,
109 isLocalUrl: isLocalUrl, 110 isLocalUrl: isLocalUrl,
110 validateDatasources: validateDatasources, 111 validateDatasources: validateDatasources,
111 - createKey: createKey,  
112 - entityTypeName: entityTypeName 112 + createKey: createKey
113 } 113 }
114 114
115 return service; 115 return service;
@@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
291 s4() + '-' + s4() + s4() + s4(); 291 s4() + '-' + s4() + s4() + s4();
292 } 292 }
293 293
  294 + function cleanCopy(object) {
  295 + var copy = angular.copy(object);
  296 + for (var prop in copy) {
  297 + if (prop && prop.startsWith('$$')) {
  298 + delete copy[prop];
  299 + }
  300 + }
  301 + return copy;
  302 + }
  303 +
294 function genNextColor(datasources) { 304 function genNextColor(datasources) {
295 var index = 0; 305 var index = 0;
296 if (datasources) { 306 if (datasources) {
@@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
347 return dataKey; 357 return dataKey;
348 } 358 }
349 359
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 -  
373 } 360 }
@@ -52,7 +52,7 @@ function Dashboard() { @@ -52,7 +52,7 @@ function Dashboard() {
52 bindToController: { 52 bindToController: {
53 widgets: '=', 53 widgets: '=',
54 widgetLayouts: '=?', 54 widgetLayouts: '=?',
55 - aliasesInfo: '=', 55 + aliasController: '=',
56 stateController: '=', 56 stateController: '=',
57 dashboardTimewindow: '=?', 57 dashboardTimewindow: '=?',
58 columns: '=', 58 columns: '=',
@@ -85,7 +85,7 @@ function Dashboard() { @@ -85,7 +85,7 @@ function Dashboard() {
85 } 85 }
86 86
87 /*@ngInject*/ 87 /*@ngInject*/
88 -function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) { 88 +function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) {
89 89
90 var highlightedMode = false; 90 var highlightedMode = false;
91 var highlightedWidget = null; 91 var highlightedWidget = null;
@@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
329 $scope.$broadcast('toggleDashboardEditMode', vm.isEdit); 329 $scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
330 }); 330 });
331 331
332 - $scope.$watch('vm.aliasesInfo.entityAliases', function () {  
333 - $scope.$broadcast('entityAliasListChanged', vm.aliasesInfo);  
334 - }, true);  
335 -  
336 $scope.$on('gridster-resized', function (event, sizes, theGridster) { 332 $scope.$on('gridster-resized', function (event, sizes, theGridster) {
337 if (checkIsLocalGridsterElement(theGridster)) { 333 if (checkIsLocalGridsterElement(theGridster)) {
338 vm.gridster = theGridster; 334 vm.gridster = theGridster;
@@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
796 } 792 }
797 793
798 function dashboardLoaded() { 794 function dashboardLoaded() {
799 - $timeout(function () { 795 + $mdUtil.nextTick(function () {
800 if (vm.dashboardTimewindowWatch) { 796 if (vm.dashboardTimewindowWatch) {
801 vm.dashboardTimewindowWatch(); 797 vm.dashboardTimewindowWatch();
802 vm.dashboardTimewindowWatch = null; 798 vm.dashboardTimewindowWatch = null;
@@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
806 }, true); 802 }, true);
807 adoptMaxRows(); 803 adoptMaxRows();
808 vm.dashboardLoading = false; 804 vm.dashboardLoading = false;
809 - $timeout(function () {  
810 - var gridsterScope = gridsterElement.scope();  
811 - vm.gridster = gridsterScope.gridster;  
812 - if (vm.onInit) {  
813 - vm.onInit({dashboard: vm}); 805 + if ($scope.gridsterScopeWatcher) {
  806 + $scope.gridsterScopeWatcher();
  807 + }
  808 + $scope.gridsterScopeWatcher = $scope.$watch(
  809 + function() {
  810 + var hasScope = gridsterElement.scope() ? true : false;
  811 + return hasScope;
  812 + },
  813 + function(hasScope) {
  814 + if (hasScope) {
  815 + $scope.gridsterScopeWatcher();
  816 + $scope.gridsterScopeWatcher = null;
  817 + var gridsterScope = gridsterElement.scope();
  818 + vm.gridster = gridsterScope.gridster;
  819 + if (vm.onInit) {
  820 + vm.onInit({dashboard: vm});
  821 + }
  822 + }
814 } 823 }
815 - }, 0, false);  
816 - }, 0, false); 824 + );
  825 + });
817 } 826 }
818 827
819 function loading() { 828 function loading() {
@@ -89,7 +89,7 @@ @@ -89,7 +89,7 @@
89 <div flex tb-widget 89 <div flex tb-widget
90 locals="{ visibleRect: vm.visibleRect, 90 locals="{ visibleRect: vm.visibleRect,
91 widget: widget, 91 widget: widget,
92 - aliasesInfo: vm.aliasesInfo, 92 + aliasController: vm.aliasController,
93 stateController: vm.stateController, 93 stateController: vm.stateController,
94 isEdit: vm.isEdit, 94 isEdit: vm.isEdit,
95 stDiff: vm.stDiff, 95 stDiff: vm.stDiff,
@@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things @@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
20 .name; 20 .name;
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey, dataKeySettingsSchema, entityAlias, entityAliases) { 23 +function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dataKey, dataKeySettingsSchema, entityAlias, aliasController) {
24 24
25 var vm = this; 25 var vm = this;
26 26
27 vm.dataKey = dataKey; 27 vm.dataKey = dataKey;
28 vm.dataKeySettingsSchema = dataKeySettingsSchema; 28 vm.dataKeySettingsSchema = dataKeySettingsSchema;
29 vm.entityAlias = entityAlias; 29 vm.entityAlias = entityAlias;
30 - vm.entityAliases = entityAliases; 30 + vm.aliasController = aliasController;
31 31
32 vm.hide = function () { 32 vm.hide = function () {
33 $mdDialog.hide(); 33 $mdDialog.hide();
@@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey @@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
38 }; 38 };
39 39
40 vm.fetchEntityKeys = function (entityAliasId, query, type) { 40 vm.fetchEntityKeys = function (entityAliasId, query, type) {
41 - var alias = vm.entityAliases[entityAliasId];  
42 - if (alias) {  
43 - return entityService.getEntityKeys(alias.entityType, alias.entityId, query, type);  
44 - } else {  
45 - return [];  
46 - } 41 + var deferred = $q.defer();
  42 + vm.aliasController.getAliasInfo(entityAliasId).then(
  43 + function success(aliasInfo) {
  44 + var entity = aliasInfo.currentEntity;
  45 + if (entity) {
  46 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  47 + function success(keys) {
  48 + deferred.resolve(keys);
  49 + },
  50 + function fail() {
  51 + deferred.resolve([]);
  52 + }
  53 + );
  54 + } else {
  55 + deferred.resolve([]);
  56 + }
  57 + },
  58 + function fail() {
  59 + deferred.resolve([]);
  60 + }
  61 + );
  62 + return deferred.promise;
47 }; 63 };
48 64
49 vm.save = function () { 65 vm.save = function () {
@@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
103 ngModelCtrl.$render = function () { 103 ngModelCtrl.$render = function () {
104 if (ngModelCtrl.$viewValue) { 104 if (ngModelCtrl.$viewValue) {
105 var entityAliasId = ngModelCtrl.$viewValue.entityAliasId; 105 var entityAliasId = ngModelCtrl.$viewValue.entityAliasId;
106 - if (scope.entityAliases[entityAliasId]) {  
107 - scope.entityAlias = {id: entityAliasId, alias: scope.entityAliases[entityAliasId].alias,  
108 - entityType: scope.entityAliases[entityAliasId].entityType,  
109 - entityId: scope.entityAliases[entityAliasId].entityId}; 106 + var entityAliases = scope.aliasController.getEntityAliases();
  107 + if (entityAliases[entityAliasId]) {
  108 + scope.entityAlias = entityAliases[entityAliasId];
110 } else { 109 } else {
111 scope.entityAlias = null; 110 scope.entityAlias = null;
112 } 111 }
@@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
182 dataKey: angular.copy(dataKey), 181 dataKey: angular.copy(dataKey),
183 dataKeySettingsSchema: scope.datakeySettingsSchema, 182 dataKeySettingsSchema: scope.datakeySettingsSchema,
184 entityAlias: scope.entityAlias, 183 entityAlias: scope.entityAlias,
185 - entityAliases: scope.entityAliases 184 + aliasController: scope.aliasController
186 }, 185 },
187 parent: angular.element($document[0].body), 186 parent: angular.element($document[0].body),
188 fullscreen: true, 187 fullscreen: true,
@@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc @@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
236 require: "^ngModel", 235 require: "^ngModel",
237 scope: { 236 scope: {
238 widgetType: '=', 237 widgetType: '=',
239 - entityAliases: '=', 238 + aliasController: '=',
240 datakeySettingsSchema: '=', 239 datakeySettingsSchema: '=',
241 generateDataKey: '&', 240 generateDataKey: '&',
242 fetchEntityKeys: '&', 241 fetchEntityKeys: '&',
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> 18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
19 <tb-entity-alias-select 19 <tb-entity-alias-select
20 tb-required="true" 20 tb-required="true"
21 - entity-aliases="entityAliases" 21 + alias-controller="aliasController"
22 ng-model="entityAlias" 22 ng-model="entityAlias"
23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> 23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
24 </tb-entity-alias-select> 24 </tb-entity-alias-select>
@@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
71 } 71 }
72 }, true); 72 }, true);
73 73
  74 + scope.$watch('datasourceName', function () {
  75 + if (ngModelCtrl.$viewValue) {
  76 + ngModelCtrl.$viewValue.name = scope.datasourceName;
  77 + scope.updateValidity();
  78 + }
  79 + });
  80 +
74 ngModelCtrl.$render = function () { 81 ngModelCtrl.$render = function () {
75 if (ngModelCtrl.$viewValue) { 82 if (ngModelCtrl.$viewValue) {
76 var funcDataKeys = []; 83 var funcDataKeys = [];
@@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
78 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys); 85 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
79 } 86 }
80 scope.funcDataKeys = funcDataKeys; 87 scope.funcDataKeys = funcDataKeys;
  88 + scope.datasourceName = ngModelCtrl.$viewValue.name;
81 } 89 }
82 }; 90 };
83 91
@@ -15,23 +15,29 @@ @@ -15,23 +15,29 @@
15 */ 15 */
16 @import '../../scss/constants'; 16 @import '../../scss/constants';
17 17
18 -.tb-func-datakey-autocomplete {  
19 - .tb-not-found {  
20 - display: block;  
21 - line-height: 1.5;  
22 - height: 48px;  
23 - .tb-no-entries {  
24 - line-height: 48px;  
25 - } 18 +.tb-datasource-func {
  19 + @media (min-width: $layout-breakpoint-gt-sm) {
  20 + padding-left: 8px;
26 } 21 }
27 - li {  
28 - height: auto !important;  
29 - white-space: normal !important; 22 +
  23 + md-input-container.tb-datasource-name {
  24 + .md-errors-spacer {
  25 + display: none;
  26 + }
30 } 27 }
31 -}  
32 28
33 -tb-datasource-func {  
34 - @media (min-width: $layout-breakpoint-gt-sm) {  
35 - padding-left: 8px; 29 + .tb-func-datakey-autocomplete {
  30 + .tb-not-found {
  31 + display: block;
  32 + line-height: 1.5;
  33 + height: 48px;
  34 + .tb-no-entries {
  35 + line-height: 48px;
  36 + }
  37 + }
  38 + li {
  39 + height: auto !important;
  40 + white-space: normal !important;
  41 + }
36 } 42 }
37 -}  
  43 +}
@@ -15,59 +15,68 @@ @@ -15,59 +15,68 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<section flex layout='column' style="padding-left: 4px;">  
19 - <md-chips flex  
20 - id="function_datakey_chips"  
21 - ng-required="true"  
22 - ng-model="funcDataKeys" md-autocomplete-snap  
23 - md-transform-chip="transformDataKeyChip($chip)"  
24 - md-require-match="false">  
25 - <md-autocomplete  
26 - md-no-cache="false"  
27 - id="dataKey"  
28 - md-selected-item="selectedDataKey"  
29 - md-search-text="dataKeySearchText"  
30 - md-items="item in dataKeysSearch(dataKeySearchText)"  
31 - md-item-text="item.name"  
32 - md-min-length="0"  
33 - placeholder="{{ 'datakey.function-types' | translate }}"  
34 - md-menu-class="tb-func-datakey-autocomplete">  
35 - <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>  
36 - <md-not-found>  
37 - <div class="tb-not-found">  
38 - <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">  
39 - <span translate>device.no-keys-found</span> 18 +<section class="tb-datasource-func" flex layout='column'
  19 + layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
  20 + <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
  21 + <input name="datasourceName"
  22 + placeholder="{{ 'datasource.name' | translate }}"
  23 + ng-model="datasourceName"
  24 + aria-label="{{ 'datasource.name' | translate }}">
  25 + </md-input-container>
  26 + <section flex layout='column' style="padding-left: 4px;">
  27 + <md-chips flex
  28 + id="function_datakey_chips"
  29 + ng-required="true"
  30 + ng-model="funcDataKeys" md-autocomplete-snap
  31 + md-transform-chip="transformDataKeyChip($chip)"
  32 + md-require-match="false">
  33 + <md-autocomplete
  34 + md-no-cache="false"
  35 + id="dataKey"
  36 + md-selected-item="selectedDataKey"
  37 + md-search-text="dataKeySearchText"
  38 + md-items="item in dataKeysSearch(dataKeySearchText)"
  39 + md-item-text="item.name"
  40 + md-min-length="0"
  41 + placeholder="{{ 'datakey.function-types' | translate }}"
  42 + md-menu-class="tb-func-datakey-autocomplete">
  43 + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
  44 + <md-not-found>
  45 + <div class="tb-not-found">
  46 + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
  47 + <span translate>device.no-keys-found</span>
  48 + </div>
  49 + <div ng-if="textIsNotEmpty(dataKeySearchText)">
  50 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
  51 + <span>
  52 + <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
  53 + </span>
  54 + </div>
40 </div> 55 </div>
41 - <div ng-if="textIsNotEmpty(dataKeySearchText)">  
42 - <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>  
43 - <span>  
44 - <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>  
45 - </span>  
46 - </div>  
47 - </div>  
48 - </md-not-found>  
49 - </md-autocomplete>  
50 - <md-chip-template>  
51 - <div layout="row" layout-align="start center" class="tb-attribute-chip">  
52 - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">  
53 - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>  
54 - </div>  
55 - <div layout="row" flex>  
56 - <div class="tb-chip-label">  
57 - {{$chip.label}} 56 + </md-not-found>
  57 + </md-autocomplete>
  58 + <md-chip-template>
  59 + <div layout="row" layout-align="start center" class="tb-attribute-chip">
  60 + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
  61 + <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
58 </div> 62 </div>
59 - <div class="tb-chip-separator">: </div>  
60 - <div class="tb-chip-label">  
61 - <strong>{{$chip.name}}</strong> 63 + <div layout="row" flex>
  64 + <div class="tb-chip-label">
  65 + {{$chip.label}}
  66 + </div>
  67 + <div class="tb-chip-separator">: </div>
  68 + <div class="tb-chip-label">
  69 + <strong>{{$chip.name}}</strong>
  70 + </div>
62 </div> 71 </div>
  72 + <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
  73 + <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
  74 + </md-button>
63 </div> 75 </div>
64 - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">  
65 - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>  
66 - </md-button>  
67 - </div>  
68 - </md-chip-template>  
69 - </md-chips>  
70 - <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">  
71 - <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>  
72 - </div> 76 + </md-chip-template>
  77 + </md-chips>
  78 + <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
  79 + <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
  80 + </div>
  81 + </section>
73 </section> 82 </section>
@@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) { @@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
76 restrict: "E", 76 restrict: "E",
77 require: "^ngModel", 77 require: "^ngModel",
78 scope: { 78 scope: {
79 - entityAliases: '=', 79 + aliasController: '=',
80 widgetType: '=', 80 widgetType: '=',
81 functionsOnly: '=', 81 functionsOnly: '=',
82 datakeySettingsSchema: '=', 82 datakeySettingsSchema: '=',
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 ng-switch-when="entity" 37 ng-switch-when="entity"
38 ng-required="model.type === types.datasourceType.entity" 38 ng-required="model.type === types.datasourceType.entity"
39 widget-type="widgetType" 39 widget-type="widgetType"
40 - entity-aliases="entityAliases" 40 + alias-controller="aliasController"
41 generate-data-key="generateDataKey({chip: chip, type: type})" 41 generate-data-key="generateDataKey({chip: chip, type: type})"
42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})" 42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})"> 43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
@@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', []) @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
31 .name; 31 .name;
32 32
33 /*@ngInject*/ 33 /*@ngInject*/
34 -function EntityAliasSelect($compile, $templateCache, $mdConstant) { 34 +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
35 35
36 var linker = function (scope, element, attrs, ngModelCtrl) { 36 var linker = function (scope, element, attrs, ngModelCtrl) {
37 var template = $templateCache.get(entityAliasSelectTemplate); 37 var template = $templateCache.get(entityAliasSelectTemplate);
@@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
49 ngModelCtrl.$setValidity('entityAlias', valid); 49 ngModelCtrl.$setValidity('entityAlias', valid);
50 }; 50 };
51 51
52 - scope.$watch('entityAliases', function () { 52 + scope.$watch('aliasController', function () {
53 scope.entityAliasList = []; 53 scope.entityAliasList = [];
54 - for (var aliasId in scope.entityAliases) { 54 + var entityAliases = scope.aliasController.getEntityAliases();
  55 + for (var aliasId in entityAliases) {
55 if (scope.allowedEntityTypes) { 56 if (scope.allowedEntityTypes) {
56 - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) { 57 + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
57 continue; 58 continue;
58 } 59 }
59 } 60 }
60 - var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias,  
61 - entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId};  
62 - scope.entityAliasList.push(entityAlias); 61 + scope.entityAliasList.push(entityAliases[aliasId]);
63 } 62 }
64 - }, true); 63 + });
65 64
66 scope.$watch('entityAlias', function () { 65 scope.$watch('entityAlias', function () {
67 scope.updateView(); 66 scope.updateView();
@@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) { @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
141 link: linker, 140 link: linker,
142 scope: { 141 scope: {
143 tbRequired: '=?', 142 tbRequired: '=?',
144 - entityAliases: '=', 143 + aliasController: '=',
145 allowedEntityTypes: '=?', 144 allowedEntityTypes: '=?',
146 onCreateEntityAlias: '&' 145 onCreateEntityAlias: '&'
147 } 146 }
@@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) { 128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) { 129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
130 var aliasId = config.targetDeviceAliasIds[0]; 130 var aliasId = config.targetDeviceAliasIds[0];
131 - if (scope.entityAliases[aliasId]) {  
132 - scope.targetDeviceAlias.value = {  
133 - id: aliasId,  
134 - alias: scope.entityAliases[aliasId].alias,  
135 - entityType: scope.entityAliases[aliasId].entityType,  
136 - entityId: scope.entityAliases[aliasId].entityId  
137 - }; 131 + var entityAliases = scope.aliasController.getEntityAliases();
  132 + if (entityAliases[aliasId]) {
  133 + scope.targetDeviceAlias.value = entityAliases[aliasId];
138 } else { 134 } else {
139 scope.targetDeviceAlias.value = null; 135 scope.targetDeviceAlias.value = null;
140 } 136 }
@@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
395 widgetType: '=', 391 widgetType: '=',
396 widgetSettingsSchema: '=', 392 widgetSettingsSchema: '=',
397 datakeySettingsSchema: '=', 393 datakeySettingsSchema: '=',
398 - entityAliases: '=', 394 + aliasController: '=',
399 functionsOnly: '=', 395 functionsOnly: '=',
400 fetchEntityKeys: '&', 396 fetchEntityKeys: '&',
401 onCreateEntityAlias: '&', 397 onCreateEntityAlias: '&',
@@ -60,7 +60,7 @@ @@ -60,7 +60,7 @@
60 style="padding: 0 0 0 10px; margin: 5px;"> 60 style="padding: 0 0 0 10px; margin: 5px;">
61 <tb-datasource flex ng-model="datasource.value" 61 <tb-datasource flex ng-model="datasource.value"
62 widget-type="widgetType" 62 widget-type="widgetType"
63 - entity-aliases="entityAliases" 63 + alias-controller="aliasController"
64 functions-only="functionsOnly" 64 functions-only="functionsOnly"
65 datakey-settings-schema="datakeySettingsSchema" 65 datakey-settings-schema="datakeySettingsSchema"
66 generate-data-key="generateDataKey(chip,type)" 66 generate-data-key="generateDataKey(chip,type)"
@@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
104 <v-pane-content style="padding: 0 5px;"> 104 <v-pane-content style="padding: 0 5px;">
105 <tb-entity-alias-select flex 105 <tb-entity-alias-select flex
106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode" 106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
107 - entity-aliases="entityAliases" 107 + alias-controller="aliasController"
108 allowed-entity-types="[types.entityType.device]" 108 allowed-entity-types="[types.entityType.device]"
109 ng-model="targetDeviceAlias.value" 109 ng-model="targetDeviceAlias.value"
110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})"> 110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">
@@ -20,9 +20,9 @@ import Subscription from '../api/subscription'; @@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
20 /* eslint-disable angular/angularelement */ 20 /* eslint-disable angular/angularelement */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService, 23 +export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow, 24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
25 - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) { 25 + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
26 26
27 var vm = this; 27 var vm = this;
28 28
@@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
37 $scope.executingRpcRequest = false; 37 $scope.executingRpcRequest = false;
38 38
39 var gridsterItemInited = false; 39 var gridsterItemInited = false;
  40 + var subscriptionInited = false;
  41 + var widgetSizeDetected = false;
40 42
41 var cafs = {}; 43 var cafs = {};
42 44
43 - /*  
44 - * data = array of datasourceData  
45 - * datasourceData = {  
46 - * tbDatasource,  
47 - * dataKey, { name, config }  
48 - * data = array of [time, value]  
49 - * }  
50 - */  
51 -  
52 var widgetContext = { 45 var widgetContext = {
53 inited: false, 46 inited: false,
54 $scope: $scope, 47 $scope: $scope,
55 - $container: $('#container', $element),  
56 - $containerParent: $($element), 48 + $container: null,
  49 + $containerParent: null,
57 width: 0, 50 width: 0,
58 height: 0, 51 height: 0,
59 isEdit: isEdit, 52 isEdit: isEdit,
@@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
80 createSubscription: function(options, subscribe) { 73 createSubscription: function(options, subscribe) {
81 return createSubscription(options, subscribe); 74 return createSubscription(options, subscribe);
82 }, 75 },
83 -  
84 -  
85 - // type: "timeseries" or "latest" or "rpc"  
86 - /* subscriptionInfo = [  
87 - {  
88 - entityType: ""  
89 - entityId: ""  
90 - entityName: ""  
91 - timeseries: [{ name: "", label: "" }, ..]  
92 - attributes: [{ name: "", label: "" }, ..]  
93 - }  
94 - ..  
95 - ]*/  
96 -  
97 - // options = {  
98 - // timeWindowConfig,  
99 - // useDashboardTimewindow,  
100 - // legendConfig,  
101 - // decimals,  
102 - // units,  
103 - // callbacks [ onDataUpdated(subscription, apply) ]  
104 - // }  
105 - //  
106 -  
107 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) { 76 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
108 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe); 77 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
109 }, 78 },
@@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
149 dashboardTimewindowApi: dashboardTimewindowApi, 118 dashboardTimewindowApi: dashboardTimewindowApi,
150 types: types, 119 types: types,
151 stDiff: stDiff, 120 stDiff: stDiff,
152 - aliasesInfo: aliasesInfo 121 + aliasController: aliasController
153 }; 122 };
154 123
155 var widgetTypeInstance; 124 var widgetTypeInstance;
@@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
203 172
204 vm.gridsterItemInitialized = gridsterItemInitialized; 173 vm.gridsterItemInitialized = gridsterItemInitialized;
205 174
206 - initialize();  
207 -  
208 -  
209 - /*  
210 - options = {  
211 - type,  
212 - targetDeviceAliasIds, // RPC  
213 - targetDeviceIds, // RPC  
214 - datasources,  
215 - timeWindowConfig,  
216 - useDashboardTimewindow,  
217 - legendConfig,  
218 - decimals,  
219 - units,  
220 - callbacks  
221 - }  
222 - */ 175 + initialize().then(
  176 + function(){
  177 + onInit();
  178 + }
  179 + );
223 180
224 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) { 181 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
225 var deferred = $q.defer(); 182 var deferred = $q.defer();
@@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
233 } 190 }
234 } 191 }
235 192
236 - entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then( 193 + entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then(
237 function (datasources) { 194 function (datasources) {
238 options.datasources = datasources; 195 options.datasources = datasources;
239 - var subscription = createSubscription(options, subscribe);  
240 - if (useDefaultComponents) {  
241 - defaultSubscriptionOptions(subscription, options);  
242 - }  
243 - deferred.resolve(subscription); 196 + createSubscription(options, subscribe).then(
  197 + function success(subscription) {
  198 + if (useDefaultComponents) {
  199 + defaultSubscriptionOptions(subscription, options);
  200 + }
  201 + deferred.resolve(subscription);
  202 + },
  203 + function fail() {
  204 + deferred.reject();
  205 + }
  206 + );
244 } 207 }
245 ); 208 );
246 return deferred.promise; 209 return deferred.promise;
247 } 210 }
248 211
249 function createSubscription(options, subscribe) { 212 function createSubscription(options, subscribe) {
  213 + var deferred = $q.defer();
250 options.dashboardTimewindow = dashboardTimewindow; 214 options.dashboardTimewindow = dashboardTimewindow;
251 - var subscription =  
252 - new Subscription(subscriptionContext, options);  
253 - widgetContext.subscriptions[subscription.id] = subscription;  
254 - if (subscribe) {  
255 - subscription.subscribe();  
256 - }  
257 - return subscription; 215 + new Subscription(subscriptionContext, options).then(
  216 + function success(subscription) {
  217 + widgetContext.subscriptions[subscription.id] = subscription;
  218 + if (subscribe) {
  219 + subscription.subscribe();
  220 + }
  221 + deferred.resolve(subscription);
  222 + },
  223 + function fail() {
  224 + deferred.reject();
  225 + }
  226 + );
  227 +
  228 + return deferred.promise;
258 } 229 }
259 230
260 function defaultComponentsOptions(options) { 231 function defaultComponentsOptions(options) {
@@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
310 } 281 }
311 282
312 function createDefaultSubscription() { 283 function createDefaultSubscription() {
313 - var subscription;  
314 var options; 284 var options;
  285 + var deferred = $q.defer();
315 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { 286 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
316 options = { 287 options = {
317 type: widget.type, 288 type: widget.type,
@@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
319 }; 290 };
320 defaultComponentsOptions(options); 291 defaultComponentsOptions(options);
321 292
322 - subscription = createSubscription(options);  
323 -  
324 - defaultSubscriptionOptions(subscription, options); 293 + createSubscription(options).then(
  294 + function success(subscription) {
  295 + defaultSubscriptionOptions(subscription, options);
325 296
326 - // backward compatibility 297 + // backward compatibility
327 298
328 - widgetContext.datasources = subscription.datasources;  
329 - widgetContext.data = subscription.data;  
330 - widgetContext.hiddenData = subscription.hiddenData;  
331 - widgetContext.timeWindow = subscription.timeWindow; 299 + widgetContext.datasources = subscription.datasources;
  300 + widgetContext.data = subscription.data;
  301 + widgetContext.hiddenData = subscription.hiddenData;
  302 + widgetContext.timeWindow = subscription.timeWindow;
  303 + widgetContext.defaultSubscription = subscription;
  304 + deferred.resolve();
  305 + },
  306 + function fail() {
  307 + deferred.reject();
  308 + }
  309 + );
332 310
333 } else if (widget.type === types.widgetType.rpc.value) { 311 } else if (widget.type === types.widgetType.rpc.value) {
334 $scope.loadingData = false; 312 $scope.loadingData = false;
@@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
356 $scope.rpcRejection = null; 334 $scope.rpcRejection = null;
357 } 335 }
358 } 336 }
359 - subscription = createSubscription(options); 337 + createSubscription(options).then(
  338 + function success(subscription) {
  339 + widgetContext.defaultSubscription = subscription;
  340 + deferred.resolve();
  341 + },
  342 + function fail() {
  343 + deferred.reject();
  344 + }
  345 + );
360 } else if (widget.type === types.widgetType.static.value) { 346 } else if (widget.type === types.widgetType.static.value) {
361 $scope.loadingData = false; 347 $scope.loadingData = false;
  348 + deferred.resolve();
  349 + } else {
  350 + deferred.resolve();
362 } 351 }
363 - if (subscription) {  
364 - widgetContext.defaultSubscription = subscription;  
365 - } 352 + return deferred.promise;
366 } 353 }
367 354
  355 + function configureWidgetElement() {
368 356
369 - function initialize() { 357 + $scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
  358 + widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
370 359
371 - if (!vm.useCustomDatasources) {  
372 - createDefaultSubscription(); 360 + if ($scope.displayLegend) {
  361 + $scope.legendConfig = widget.config.legendConfig ||
  362 + {
  363 + position: types.position.bottom.value,
  364 + showMin: false,
  365 + showMax: false,
  366 + showAvg: widget.type === types.widgetType.timeseries.value,
  367 + showTotal: false
  368 + };
  369 + $scope.legendData = {
  370 + keys: [],
  371 + data: []
  372 + };
  373 + }
  374 +
  375 + var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
  376 + '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
  377 + '</div>' +
  378 + '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
  379 + '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
  380 + '</div>';
  381 +
  382 + var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
  383 + if ($scope.displayLegend) {
  384 + var layoutType;
  385 + if ($scope.legendConfig.position === types.position.top.value ||
  386 + $scope.legendConfig.position === types.position.bottom.value) {
  387 + layoutType = 'column';
  388 + } else {
  389 + layoutType = 'row';
  390 + }
  391 +
  392 + var legendStyle;
  393 + switch($scope.legendConfig.position) {
  394 + case types.position.top.value:
  395 + legendStyle = 'padding-bottom: 8px;';
  396 + break;
  397 + case types.position.bottom.value:
  398 + legendStyle = 'padding-top: 8px;';
  399 + break;
  400 + case types.position.left.value:
  401 + legendStyle = 'padding-right: 0px;';
  402 + break;
  403 + case types.position.right.value:
  404 + legendStyle = 'padding-left: 0px;';
  405 + break;
  406 + }
  407 +
  408 + var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
  409 + containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
  410 + html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
  411 + if ($scope.legendConfig.position === types.position.top.value ||
  412 + $scope.legendConfig.position === types.position.left.value) {
  413 + html += legendHtml;
  414 + html += containerHtml;
  415 + } else {
  416 + html += containerHtml;
  417 + html += legendHtml;
  418 + }
  419 + html += '</div>';
373 } else { 420 } else {
374 - $scope.loadingData = false; 421 + html += containerHtml;
375 } 422 }
376 423
  424 + //TODO:
  425 + /*if (progressElement) {
  426 + progressScope.$destroy();
  427 + progressScope = null;
  428 +
  429 + progressElement.remove();
  430 + progressElement = null;
  431 + }*/
  432 +
  433 + $element.html(html);
  434 +
  435 + var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element;
  436 + widgetContext.$container = $('#container', containerElement);
  437 + widgetContext.$containerParent = $(containerElement);
  438 +
  439 + $compile($element.contents())($scope);
  440 +
  441 + addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
  442 + }
  443 +
  444 + function destroyWidgetElement() {
  445 + removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
  446 + $element.html('');
  447 + widgetContext.$container = null;
  448 + widgetContext.$containerParent = null;
  449 + }
  450 +
  451 + function initialize() {
  452 +
377 $scope.$on('toggleDashboardEditMode', function (event, isEdit) { 453 $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
378 onEditModeChanged(isEdit); 454 onEditModeChanged(isEdit);
379 }); 455 });
380 456
381 - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef  
382 -  
383 $scope.$watch(function () { 457 $scope.$watch(function () {
384 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; 458 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
385 }, function () { 459 }, function () {
@@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
398 onMobileModeChanged(newIsMobile); 472 onMobileModeChanged(newIsMobile);
399 }); 473 });
400 474
401 - $scope.$on('entityAliasListChanged', function (event, aliasesInfo) {  
402 - subscriptionContext.aliasesInfo = aliasesInfo; 475 + $scope.$on('entityAliasesChanged', function (event, aliasIds) {
  476 + var subscriptionChanged = false;
403 for (var id in widgetContext.subscriptions) { 477 for (var id in widgetContext.subscriptions) {
404 var subscription = widgetContext.subscriptions[id]; 478 var subscription = widgetContext.subscriptions[id];
405 - subscription.onAliasesChanged(); 479 + subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
  480 + }
  481 + if (subscriptionChanged && !vm.useCustomDatasources) {
  482 + reInit();
406 } 483 }
407 }); 484 });
408 485
409 $scope.$on("$destroy", function () { 486 $scope.$on("$destroy", function () {
410 - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef  
411 onDestroy(); 487 onDestroy();
412 }); 488 });
  489 +
  490 + configureWidgetElement();
  491 + var deferred = $q.defer();
  492 + if (!vm.useCustomDatasources) {
  493 + createDefaultSubscription().then(
  494 + function success() {
  495 + subscriptionInited = true;
  496 + deferred.resolve();
  497 + },
  498 + function fail() {
  499 + subscriptionInited = true;
  500 + deferred.reject();
  501 + }
  502 + );
  503 + } else {
  504 + $scope.loadingData = false;
  505 + subscriptionInited = true;
  506 + deferred.resolve();
  507 + }
  508 + return deferred.promise;
  509 + }
  510 +
  511 + function reInit() {
  512 + onDestroy();
  513 + configureWidgetElement();
  514 + if (!vm.useCustomDatasources) {
  515 + createDefaultSubscription().then(
  516 + function success() {
  517 + subscriptionInited = true;
  518 + onInit();
  519 + },
  520 + function fail() {
  521 + subscriptionInited = true;
  522 + onInit();
  523 + }
  524 + );
  525 + } else {
  526 + subscriptionInited = true;
  527 + onInit();
  528 + }
413 } 529 }
414 530
415 function handleWidgetException(e) { 531 function handleWidgetException(e) {
@@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
417 $scope.widgetErrorData = utils.processWidgetException(e); 533 $scope.widgetErrorData = utils.processWidgetException(e);
418 } 534 }
419 535
420 - function onInit() {  
421 - if (!widgetContext.inited) { 536 + function isReady() {
  537 + return subscriptionInited && gridsterItemInited && widgetSizeDetected;
  538 + }
  539 +
  540 + function onInit(skipSizeCheck) {
  541 + if (!skipSizeCheck) {
  542 + checkSize();
  543 + }
  544 + if (!widgetContext.inited && isReady()) {
422 widgetContext.inited = true; 545 widgetContext.inited = true;
423 try { 546 try {
424 widgetTypeInstance.onInit(); 547 widgetTypeInstance.onInit();
@@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
443 widgetContext.width = width; 566 widgetContext.width = width;
444 widgetContext.height = height; 567 widgetContext.height = height;
445 sizeChanged = true; 568 sizeChanged = true;
  569 + widgetSizeDetected = true;
446 } 570 }
447 } 571 }
448 return sizeChanged; 572 return sizeChanged;
@@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
462 handleWidgetException(e); 586 handleWidgetException(e);
463 } 587 }
464 }); 588 });
465 - } else if (gridsterItemInited) {  
466 - onInit(); 589 + } else {
  590 + onInit(true);
467 } 591 }
468 } 592 }
469 } 593 }
@@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
472 if (item && item.gridster) { 596 if (item && item.gridster) {
473 widgetContext.isMobile = item.gridster.isMobile; 597 widgetContext.isMobile = item.gridster.isMobile;
474 gridsterItemInited = true; 598 gridsterItemInited = true;
475 - if (checkSize()) {  
476 - onInit();  
477 - } 599 + onInit();
478 // gridsterItemElement = $(item.$element); 600 // gridsterItemElement = $(item.$element);
479 //updateVisibility(); 601 //updateVisibility();
480 } 602 }
@@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
544 var subscription = widgetContext.subscriptions[id]; 666 var subscription = widgetContext.subscriptions[id];
545 subscription.destroy(); 667 subscription.destroy();
546 } 668 }
  669 + subscriptionInited = false;
547 widgetContext.subscriptions = []; 670 widgetContext.subscriptions = [];
548 if (widgetContext.inited) { 671 if (widgetContext.inited) {
549 widgetContext.inited = false; 672 widgetContext.inited = false;
@@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q @@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
559 handleWidgetException(e); 682 handleWidgetException(e);
560 } 683 }
561 } 684 }
  685 + destroyWidgetElement();
562 } 686 }
563 687
564 //TODO: widgets visibility 688 //TODO: widgets visibility
@@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
28 .name; 28 .name;
29 29
30 /*@ngInject*/ 30 /*@ngInject*/
31 -function Widget($controller, $compile, types, widgetService) { 31 +function Widget($controller, widgetService) {
32 return { 32 return {
33 scope: true, 33 scope: true,
34 link: function (scope, elem, attrs) { 34 link: function (scope, elem, attrs) {
@@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) { @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
81 81
82 elem.addClass(widgetNamespace); 82 elem.addClass(widgetNamespace);
83 83
84 - var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +  
85 - '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +  
86 - '</div>' +  
87 - '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +  
88 - '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +  
89 - '</div>';  
90 -  
91 - scope.displayLegend = angular.isDefined(widget.config.showLegend) ?  
92 - widget.config.showLegend : widget.type === types.widgetType.timeseries.value;  
93 -  
94 -  
95 - var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';  
96 - if (scope.displayLegend) {  
97 - scope.legendConfig = widget.config.legendConfig ||  
98 - {  
99 - position: types.position.bottom.value,  
100 - showMin: false,  
101 - showMax: false,  
102 - showAvg: widget.type === types.widgetType.timeseries.value,  
103 - showTotal: false  
104 - };  
105 - scope.legendData = {  
106 - keys: [],  
107 - data: []  
108 - };  
109 -  
110 - var layoutType;  
111 - if (scope.legendConfig.position === types.position.top.value ||  
112 - scope.legendConfig.position === types.position.bottom.value) {  
113 - layoutType = 'column';  
114 - } else {  
115 - layoutType = 'row';  
116 - }  
117 -  
118 - var legendStyle;  
119 - switch(scope.legendConfig.position) {  
120 - case types.position.top.value:  
121 - legendStyle = 'padding-bottom: 8px;';  
122 - break;  
123 - case types.position.bottom.value:  
124 - legendStyle = 'padding-top: 8px;';  
125 - break;  
126 - case types.position.left.value:  
127 - legendStyle = 'padding-right: 0px;';  
128 - break;  
129 - case types.position.right.value:  
130 - legendStyle = 'padding-left: 0px;';  
131 - break;  
132 - }  
133 -  
134 - var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';  
135 - containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';  
136 - html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';  
137 - if (scope.legendConfig.position === types.position.top.value ||  
138 - scope.legendConfig.position === types.position.left.value) {  
139 - html += legendHtml;  
140 - html += containerHtml;  
141 - } else {  
142 - html += containerHtml;  
143 - html += legendHtml;  
144 - }  
145 - html += '</div>';  
146 - } else {  
147 - html += containerHtml;  
148 - }  
149 -  
150 - //TODO:  
151 - /*if (progressElement) {  
152 - progressScope.$destroy();  
153 - progressScope = null;  
154 -  
155 - progressElement.remove();  
156 - progressElement = null;  
157 - }*/  
158 -  
159 - elem.html(html);  
160 -  
161 - var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem;  
162 -  
163 - $compile(elem.contents())(scope);  
164 -  
165 var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType); 84 var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
166 85
167 - angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType}); 86 + angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType});
168 87
169 widgetController = $controller('WidgetController', locals); 88 widgetController = $controller('WidgetController', locals);
170 89
@@ -48,11 +48,16 @@ @@ -48,11 +48,16 @@
48 disable-attribute-scope-selection="true"> 48 disable-attribute-scope-selection="true">
49 </tb-attribute-table> 49 </tb-attribute-table>
50 </md-tab> 50 </md-tab>
  51 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
  52 + <tb-alarm-table flex entity-type="vm.types.entityType.customer"
  53 + entity-id="vm.grid.operatingItem().id.id">
  54 + </tb-alarm-table>
  55 + </md-tab>
51 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}"> 56 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}">
52 <tb-event-table flex entity-type="vm.types.entityType.customer" 57 <tb-event-table flex entity-type="vm.types.entityType.customer"
53 entity-id="vm.grid.operatingItem().id.id" 58 entity-id="vm.grid.operatingItem().id.id"
54 tenant-id="vm.grid.operatingItem().tenantId.id" 59 tenant-id="vm.grid.operatingItem().tenantId.id"
55 - default-event-type="{{vm.types.eventType.alarm.value}}"> 60 + default-event-type="{{vm.types.eventType.error.value}}">
56 </tb-event-table> 61 </tb-event-table>
57 </md-tab> 62 </md-tab>
58 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> 63 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
@@ -15,17 +15,18 @@ @@ -15,17 +15,18 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 19
20 /* eslint-enable import/no-unresolved, import/default */ 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) { 23 +export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard,
  24 + aliasController, widget, widgetInfo) {
24 25
25 var vm = this; 26 var vm = this;
26 27
27 vm.dashboard = dashboard; 28 vm.dashboard = dashboard;
28 - vm.aliasesInfo = aliasesInfo; 29 + vm.aliasController = aliasController;
29 vm.widget = widget; 30 vm.widget = widget;
30 vm.widgetInfo = widgetInfo; 31 vm.widgetInfo = widgetInfo;
31 32
@@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
85 } 86 }
86 87
87 function cancel () { 88 function cancel () {
88 - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo}); 89 + $mdDialog.cancel();
89 } 90 }
90 91
91 function add () { 92 function add () {
@@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService @@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
94 vm.widget.config = vm.widgetConfig.config; 95 vm.widget.config = vm.widgetConfig.config;
95 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder; 96 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
96 vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight; 97 vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight;
97 - $mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo}); 98 + $mdDialog.hide({widget: vm.widget});
98 } 99 }
99 } 100 }
100 101
101 function fetchEntityKeys (entityAliasId, query, type) { 102 function fetchEntityKeys (entityAliasId, query, type) {
102 - var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId];  
103 - if (entityAlias && entityAlias.entityId) {  
104 - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);  
105 - } else {  
106 - return $q.when([]);  
107 - } 103 + var deferred = $q.defer();
  104 + vm.aliasController.getAliasInfo(entityAliasId).then(
  105 + function success(aliasInfo) {
  106 + var entity = aliasInfo.currentEntity;
  107 + if (entity) {
  108 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  109 + function success(keys) {
  110 + deferred.resolve(keys);
  111 + },
  112 + function fail() {
  113 + deferred.resolve([]);
  114 + }
  115 + );
  116 + } else {
  117 + deferred.resolve([]);
  118 + }
  119 + },
  120 + function fail() {
  121 + deferred.resolve([]);
  122 + }
  123 + );
  124 + return deferred.promise;
108 } 125 }
109 126
110 function createEntityAlias (event, alias, allowedEntityTypes) { 127 function createEntityAlias (event, alias, allowedEntityTypes) {
111 128
112 var deferred = $q.defer(); 129 var deferred = $q.defer();
113 - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; 130 + var singleEntityAlias = {id: null, alias: alias, filter: {}};
114 131
115 $mdDialog.show({ 132 $mdDialog.show({
116 - controller: 'EntityAliasesController', 133 + controller: 'EntityAliasDialogController',
117 controllerAs: 'vm', 134 controllerAs: 'vm',
118 - templateUrl: entityAliasesTemplate, 135 + templateUrl: entityAliasDialogTemplate,
119 locals: { 136 locals: {
120 - config: {  
121 - entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),  
122 - widgets: null,  
123 - isSingleEntityAlias: true,  
124 - singleEntityAlias: singleEntityAlias,  
125 - allowedEntityTypes: allowedEntityTypes  
126 - } 137 + isAdd: true,
  138 + allowedEntityTypes: allowedEntityTypes,
  139 + entityAliases: vm.dashboard.configuration.entityAliases,
  140 + alias: singleEntityAlias
127 }, 141 },
128 parent: angular.element($document[0].body), 142 parent: angular.element($document[0].body),
129 fullscreen: true, 143 fullscreen: true,
130 skipHide: true, 144 skipHide: true,
131 targetEvent: event 145 targetEvent: event
132 }).then(function (singleEntityAlias) { 146 }).then(function (singleEntityAlias) {
133 - vm.dashboard.configuration.entityAliases[singleEntityAlias.id] =  
134 - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };  
135 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then(  
136 - function(resolution) {  
137 - if (!resolution.error) {  
138 - vm.aliasesInfo = resolution.aliasesInfo;  
139 - }  
140 - deferred.resolve(singleEntityAlias);  
141 - }  
142 - ); 147 + vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
  148 + vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
  149 + deferred.resolve(singleEntityAlias);
143 }, function () { 150 }, function () {
144 deferred.reject(); 151 deferred.reject();
145 }); 152 });
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 ng-model="vm.widgetConfig" 37 ng-model="vm.widgetConfig"
38 widget-settings-schema="vm.settingsSchema" 38 widget-settings-schema="vm.settingsSchema"
39 datakey-settings-schema="vm.dataKeySettingsSchema" 39 datakey-settings-schema="vm.dataKeySettingsSchema"
40 - entity-aliases="vm.aliasesInfo.entityAliases" 40 + alias-controller="vm.aliasController"
41 functions-only="vm.functionsOnly" 41 functions-only="vm.functionsOnly"
42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)" 42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)" 43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html'; 19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html'; 20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html'; 21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
@@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html' @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
24 24
25 /* eslint-enable import/no-unresolved, import/default */ 25 /* eslint-enable import/no-unresolved, import/default */
26 26
  27 +import AliasController from '../api/alias-controller';
  28 +
27 /*@ngInject*/ 29 /*@ngInject*/
28 -export default function DashboardController(types, dashboardUtils, widgetService, userService, 30 +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
29 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope, 31 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
30 $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) { 32 $scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) {
31 33
@@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
342 vm.dashboardConfiguration = vm.dashboard.configuration; 344 vm.dashboardConfiguration = vm.dashboard.configuration;
343 vm.dashboardCtx.dashboard = vm.dashboard; 345 vm.dashboardCtx.dashboard = vm.dashboard;
344 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; 346 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
  347 + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
  348 + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
345 var parentScope = $window.parent.angular.element($window.frameElement).scope(); 349 var parentScope = $window.parent.angular.element($window.frameElement).scope();
346 parentScope.$root.$broadcast('widgetEditModeInited'); 350 parentScope.$root.$broadcast('widgetEditModeInited');
347 parentScope.$root.$apply(); 351 parentScope.$root.$apply();
@@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
349 dashboardService.getDashboard($stateParams.dashboardId) 353 dashboardService.getDashboard($stateParams.dashboardId)
350 .then(function success(dashboard) { 354 .then(function success(dashboard) {
351 vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard); 355 vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
352 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases) 356 + vm.dashboardConfiguration = vm.dashboard.configuration;
  357 + vm.dashboardCtx.dashboard = vm.dashboard;
  358 + vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
  359 + vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
  360 + types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
  361 +
  362 + /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
353 .then( 363 .then(
354 function(resolution) { 364 function(resolution) {
355 if (resolution.error && !isTenantAdmin()) { 365 if (resolution.error && !isTenantAdmin()) {
@@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
362 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow; 372 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
363 } 373 }
364 } 374 }
365 - ); 375 + );*/
366 }, function fail() { 376 }, function fail() {
367 vm.configurationError = true; 377 vm.configurationError = true;
368 }); 378 });
@@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
373 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state); 383 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
374 if (layoutsData) { 384 if (layoutsData) {
375 vm.dashboardCtx.state = state; 385 vm.dashboardCtx.state = state;
  386 + vm.dashboardCtx.aliasController.dashboardStateChanged();
376 var layoutVisibilityChanged = false; 387 var layoutVisibilityChanged = false;
377 for (var l in vm.layouts) { 388 for (var l in vm.layouts) {
378 var layout = vm.layouts[l]; 389 var layout = vm.layouts[l];
@@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
916 templateUrl: addWidgetTemplate, 927 templateUrl: addWidgetTemplate,
917 locals: { 928 locals: {
918 dashboard: vm.dashboard, 929 dashboard: vm.dashboard,
919 - aliasesInfo: vm.dashboardCtx.aliasesInfo, 930 + aliasController: vm.dashboardCtx.aliasController,
920 widget: newWidget, 931 widget: newWidget,
921 widgetInfo: widgetTypeInfo 932 widgetInfo: widgetTypeInfo
922 }, 933 },
@@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
930 } 941 }
931 }).then(function (result) { 942 }).then(function (result) {
932 var widget = result.widget; 943 var widget = result.widget;
933 - vm.dashboardCtx.aliasesInfo = result.aliasesInfo;  
934 addWidget(widget); 944 addWidget(widget);
935 - }, function (rejection) {  
936 - vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo; 945 + }, function () {
937 }); 946 });
938 } 947 }
939 } 948 }
@@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
1025 notifyDashboardUpdated(); 1034 notifyDashboardUpdated();
1026 } 1035 }
1027 1036
1028 - function showAliasesResolutionError(error) { 1037 +/* function showAliasesResolutionError(error) {
1029 var alert = $mdDialog.alert() 1038 var alert = $mdDialog.alert()
1030 .parent(angular.element($document[0].body)) 1039 .parent(angular.element($document[0].body))
1031 .clickOutsideToClose(true) 1040 .clickOutsideToClose(true)
@@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService @@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
1037 alert._options.fullscreen = true; 1046 alert._options.fullscreen = true;
1038 1047
1039 $mdDialog.show(alert); 1048 $mdDialog.show(alert);
1040 - } 1049 + }*/
1041 1050
1042 function entityAliasesUpdated() { 1051 function entityAliasesUpdated() {
1043 - var deferred = $q.defer();  
1044 - entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)  
1045 - .then(  
1046 - function(resolution) {  
1047 - if (resolution.aliasesInfo) {  
1048 - vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;  
1049 - }  
1050 - deferred.resolve();  
1051 - }  
1052 - );  
1053 - return deferred.promise; 1052 + vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
1054 } 1053 }
1055 1054
1056 function notifyDashboardUpdated() { 1055 function notifyDashboardUpdated() {
@@ -57,8 +57,7 @@ @@ -57,8 +57,7 @@
57 </tb-timewindow> 57 </tb-timewindow>
58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()" 58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
59 tooltip-direction="bottom" 59 tooltip-direction="bottom"
60 - ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"  
61 - entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo"> 60 + alias-controller="vm.dashboardCtx.aliasController">
62 </tb-aliases-entity-select> 61 </tb-aliases-entity-select>
63 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button" 62 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
64 ng-click="vm.openEntityAliases($event)"> 63 ng-click="vm.openEntityAliases($event)">
@@ -179,7 +178,7 @@ @@ -179,7 +178,7 @@
179 <form name="vm.widgetForm" ng-if="vm.isEditingWidget"> 178 <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
180 <tb-edit-widget 179 <tb-edit-widget
181 dashboard="vm.dashboard" 180 dashboard="vm.dashboard"
182 - aliases-info="vm.dashboardCtx.aliasesInfo" 181 + alias-controller="vm.dashboardCtx.aliasController"
183 widget="vm.editingWidget" 182 widget="vm.editingWidget"
184 widget-layout="vm.editingWidgetLayout" 183 widget-layout="vm.editingWidgetLayout"
185 the-form="vm.widgetForm"> 184 the-form="vm.widgetForm">
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 /* eslint-disable import/no-unresolved, import/default */ 16 /* eslint-disable import/no-unresolved, import/default */
17 17
18 -import entityAliasesTemplate from '../entity/entity-aliases.tpl.html'; 18 +import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
19 import editWidgetTemplate from './edit-widget.tpl.html'; 19 import editWidgetTemplate from './edit-widget.tpl.html';
20 20
21 /* eslint-enable import/no-unresolved, import/default */ 21 /* eslint-enable import/no-unresolved, import/default */
@@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid @@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
68 }); 68 });
69 69
70 scope.fetchEntityKeys = function (entityAliasId, query, type) { 70 scope.fetchEntityKeys = function (entityAliasId, query, type) {
71 - var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId];  
72 - if (entityAlias && entityAlias.entityId) {  
73 - return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);  
74 - } else {  
75 - return $q.when([]);  
76 - } 71 + var deferred = $q.defer();
  72 + scope.aliasController.getAliasInfo(entityAliasId).then(
  73 + function success(aliasInfo) {
  74 + var entity = aliasInfo.currentEntity;
  75 + if (entity) {
  76 + entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
  77 + function success(keys) {
  78 + deferred.resolve(keys);
  79 + },
  80 + function fail() {
  81 + deferred.resolve([]);
  82 + }
  83 + );
  84 + } else {
  85 + deferred.resolve([]);
  86 + }
  87 + },
  88 + function fail() {
  89 + deferred.resolve([]);
  90 + }
  91 + );
  92 + return deferred.promise;
77 }; 93 };
78 94
79 scope.createEntityAlias = function (event, alias, allowedEntityTypes) { 95 scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
80 96
81 var deferred = $q.defer(); 97 var deferred = $q.defer();
82 - var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null}; 98 + var singleEntityAlias = {id: null, alias: alias, filter: {}};
83 99
84 $mdDialog.show({ 100 $mdDialog.show({
85 - controller: 'EntityAliasesController', 101 + controller: 'EntityAliasDialogController',
86 controllerAs: 'vm', 102 controllerAs: 'vm',
87 - templateUrl: entityAliasesTemplate, 103 + templateUrl: entityAliasDialogTemplate,
88 locals: { 104 locals: {
89 - config: {  
90 - entityAliases: angular.copy(scope.dashboard.configuration.entityAliases),  
91 - widgets: null,  
92 - isSingleEntityAlias: true,  
93 - singleEntityAlias: singleEntityAlias,  
94 - allowedEntityTypes: allowedEntityTypes  
95 - } 105 + isAdd: true,
  106 + allowedEntityTypes: allowedEntityTypes,
  107 + entityAliases: scope.dashboard.configuration.entityAliases,
  108 + alias: singleEntityAlias
96 }, 109 },
97 parent: angular.element($document[0].body), 110 parent: angular.element($document[0].body),
98 fullscreen: true, 111 fullscreen: true,
99 skipHide: true, 112 skipHide: true,
100 targetEvent: event 113 targetEvent: event
101 }).then(function (singleEntityAlias) { 114 }).then(function (singleEntityAlias) {
102 - scope.dashboard.configuration.entityAliases[singleEntityAlias.id] =  
103 - { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };  
104 - entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then(  
105 - function(resolution) {  
106 - if (!resolution.error) {  
107 - scope.aliasesInfo = resolution.aliasesInfo;  
108 - }  
109 - deferred.resolve(singleEntityAlias);  
110 - }  
111 - ); 115 + scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
  116 + scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases);
  117 + deferred.resolve(singleEntityAlias);
112 }, function () { 118 }, function () {
113 deferred.reject(); 119 deferred.reject();
114 }); 120 });
@@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid @@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
124 link: linker, 130 link: linker,
125 scope: { 131 scope: {
126 dashboard: '=', 132 dashboard: '=',
127 - aliasesInfo: '=', 133 + aliasController: '=',
128 widget: '=', 134 widget: '=',
129 widgetLayout: '=', 135 widgetLayout: '=',
130 theForm: '=' 136 theForm: '='
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 is-data-enabled="isDataEnabled" 21 is-data-enabled="isDataEnabled"
22 widget-settings-schema="settingsSchema" 22 widget-settings-schema="settingsSchema"
23 datakey-settings-schema="dataKeySettingsSchema" 23 datakey-settings-schema="dataKeySettingsSchema"
24 - entity-aliases="aliasesInfo.entityAliases" 24 + alias-controller="aliasController"
25 functions-only="functionsOnly" 25 functions-only="functionsOnly"
26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)" 26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)" 27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
@@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
45 widget-layouts="vm.layoutCtx.widgetLayouts" 45 widget-layouts="vm.layoutCtx.widgetLayouts"
46 columns="vm.layoutCtx.gridSettings.columns" 46 columns="vm.layoutCtx.gridSettings.columns"
47 margins="vm.layoutCtx.gridSettings.margins" 47 margins="vm.layoutCtx.gridSettings.margins"
48 - aliases-info="vm.dashboardCtx.aliasesInfo" 48 + alias-controller="vm.dashboardCtx.aliasController"
49 state-controller="vm.dashboardCtx.stateController" 49 state-controller="vm.dashboardCtx.stateController"
50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" 50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
51 is-edit="vm.isEdit" 51 is-edit="vm.isEdit"
@@ -49,11 +49,16 @@ @@ -49,11 +49,16 @@
49 disable-attribute-scope-selection="true"> 49 disable-attribute-scope-selection="true">
50 </tb-attribute-table> 50 </tb-attribute-table>
51 </md-tab> 51 </md-tab>
  52 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
  53 + <tb-alarm-table flex entity-type="vm.types.entityType.device"
  54 + entity-id="vm.grid.operatingItem().id.id">
  55 + </tb-alarm-table>
  56 + </md-tab>
52 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}"> 57 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
53 <tb-event-table flex entity-type="vm.types.entityType.device" 58 <tb-event-table flex entity-type="vm.types.entityType.device"
54 entity-id="vm.grid.operatingItem().id.id" 59 entity-id="vm.grid.operatingItem().id.id"
55 tenant-id="vm.grid.operatingItem().tenantId.id" 60 tenant-id="vm.grid.operatingItem().tenantId.id"
56 - default-event-type="{{vm.types.eventType.alarm.value}}"> 61 + default-event-type="{{vm.types.eventType.error.value}}">
57 </tb-event-table> 62 </tb-event-table>
58 </md-tab> 63 </md-tab>
59 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}"> 64 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 import uiRouter from 'angular-ui-router'; 16 import uiRouter from 'angular-ui-router';
17 import thingsboardGrid from '../components/grid.directive'; 17 import thingsboardGrid from '../components/grid.directive';
18 -import thingsboardEvent from '../event';  
19 import thingsboardApiUser from '../api/user.service'; 18 import thingsboardApiUser from '../api/user.service';
20 import thingsboardApiDevice from '../api/device.service'; 19 import thingsboardApiDevice from '../api/device.service';
21 import thingsboardApiCustomer from '../api/customer.service'; 20 import thingsboardApiCustomer from '../api/customer.service';
@@ -30,7 +29,6 @@ import DeviceDirective from './device.directive'; @@ -30,7 +29,6 @@ import DeviceDirective from './device.directive';
30 export default angular.module('thingsboard.device', [ 29 export default angular.module('thingsboard.device', [
31 uiRouter, 30 uiRouter,
32 thingsboardGrid, 31 thingsboardGrid,
33 - thingsboardEvent,  
34 thingsboardApiUser, 32 thingsboardApiUser,
35 thingsboardApiDevice, 33 thingsboardApiDevice,
36 thingsboardApiCustomer 34 thingsboardApiCustomer
ui/src/app/entity/alias/aliases-entity-select-button.tpl.html renamed from ui/src/app/entity/aliases-entity-select-button.tpl.html
  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 +/*@ngInject*/
  18 +export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) {
  19 +
  20 + var vm = this;
  21 + vm._mdPanelRef = mdPanelRef;
  22 + vm.aliasController = aliasController;
  23 + vm.onEntityAliasesUpdate = onEntityAliasesUpdate;
  24 + vm.entityAliases = {};
  25 + vm.entityAliasesInfo = {};
  26 +
  27 + vm.currentAliasEntityChanged = currentAliasEntityChanged;
  28 +
  29 + var allEntityAliases = vm.aliasController.getEntityAliases();
  30 + for (var aliasId in allEntityAliases) {
  31 + var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId);
  32 + if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) {
  33 + vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo);
  34 + vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id;
  35 + }
  36 + }
  37 +
  38 + function currentAliasEntityChanged(aliasId, selectedId) {
  39 + var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities;
  40 + var selected = $filter('filter')(resolvedEntities, {id: selectedId});
  41 + if (selected && selected.length) {
  42 + vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]);
  43 + if (onEntityAliasesUpdate) {
  44 + onEntityAliasesUpdate();
  45 + }
  46 + }
  47 + }
  48 +
  49 +}
ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html renamed from ui/src/app/entity/aliases-entity-select-panel.tpl.html
@@ -18,12 +18,12 @@ @@ -18,12 +18,12 @@
18 <md-content flex layout="column"> 18 <md-content flex layout="column">
19 <section flex layout="column"> 19 <section flex layout="column">
20 <md-content flex class="md-padding" layout="column"> 20 <md-content flex class="md-padding" layout="column">
21 - <div flex layout="row" ng-repeat="(aliasId, entityAlias) in vm.entityAliases"> 21 + <div flex layout="row" ng-repeat="(aliasId, entityAliasInfo) in vm.entityAliasesInfo">
22 <md-input-container flex> 22 <md-input-container flex>
23 - <label>{{entityAlias.alias}}</label>  
24 - <md-select ng-model="vm.entityAliases[aliasId].entityId">  
25 - <md-option ng-repeat="entityInfo in vm.entityAliasesInfo[aliasId]" ng-value="entityInfo.id">  
26 - {{entityInfo.name}} 23 + <label>{{entityAliasInfo.alias}}</label>
  24 + <md-select ng-model="entityAliasInfo.selectedId" ng-change="vm.currentAliasEntityChanged(aliasId, entityAliasInfo.selectedId)">
  25 + <md-option ng-repeat="resolvedEntity in entityAliasInfo.resolvedEntities" ng-value="resolvedEntity.id">
  26 + {{resolvedEntity.name}}
27 </md-option> 27 </md-option>
28 </md-select> 28 </md-select>
29 </md-input-container> 29 </md-input-container>
ui/src/app/entity/alias/aliases-entity-select.directive.js renamed from ui/src/app/entity/aliases-entity-select.directive.js
@@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl. @@ -29,7 +29,7 @@ import aliasesEntitySelectPanelTemplate from './aliases-entity-select-panel.tpl.
29 /*@ngInject*/ 29 /*@ngInject*/
30 export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) { 30 export default function AliasesEntitySelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) {
31 31
32 - var linker = function (scope, element, attrs, ngModelCtrl) { 32 + var linker = function (scope, element, attrs) {
33 33
34 /* tbAliasesEntitySelect (ng-model) 34 /* tbAliasesEntitySelect (ng-model)
35 * { 35 * {
@@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
81 position: position, 81 position: position,
82 fullscreen: false, 82 fullscreen: false,
83 locals: { 83 locals: {
84 - 'entityAliases': angular.copy(scope.model),  
85 - 'entityAliasesInfo': scope.entityAliasesInfo,  
86 - 'onEntityAliasesUpdate': function (entityAliases) {  
87 - scope.model = entityAliases; 84 + 'aliasController': scope.aliasController,
  85 + 'onEntityAliasesUpdate': function () {
88 scope.updateView(); 86 scope.updateView();
89 } 87 }
90 }, 88 },
@@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
96 $mdPanel.open(config); 94 $mdPanel.open(config);
97 } 95 }
98 96
  97 + scope.$on('entityAliasesChanged', function() {
  98 + scope.updateView();
  99 + });
  100 +
  101 + scope.$on('entityAliasResolved', function() {
  102 + scope.updateView();
  103 + });
  104 +
99 scope.updateView = function () { 105 scope.updateView = function () {
100 - var value = angular.copy(scope.model);  
101 - ngModelCtrl.$setViewValue(value);  
102 updateDisplayValue(); 106 updateDisplayValue();
103 } 107 }
104 108
105 - ngModelCtrl.$render = function () {  
106 - if (ngModelCtrl.$viewValue) {  
107 - var value = ngModelCtrl.$viewValue;  
108 - scope.model = angular.copy(value);  
109 - updateDisplayValue();  
110 - }  
111 - }  
112 -  
113 function updateDisplayValue() { 109 function updateDisplayValue() {
114 var displayValue; 110 var displayValue;
115 var singleValue = true; 111 var singleValue = true;
116 var currentAliasId; 112 var currentAliasId;
117 - for (var aliasId in scope.model) {  
118 - if (!currentAliasId) {  
119 - currentAliasId = aliasId;  
120 - } else {  
121 - singleValue = false;  
122 - break; 113 + var entityAliases = scope.aliasController.getEntityAliases();
  114 + for (var aliasId in entityAliases) {
  115 + var entityAlias = entityAliases[aliasId];
  116 + if (!entityAlias.filter.resolveMultiple) {
  117 + var resolvedAlias = scope.aliasController.getInstantAliasInfo(aliasId);
  118 + if (resolvedAlias && resolvedAlias.currentEntity) {
  119 + if (!currentAliasId) {
  120 + currentAliasId = aliasId;
  121 + } else {
  122 + singleValue = false;
  123 + break;
  124 + }
  125 + }
123 } 126 }
124 } 127 }
125 if (singleValue && currentAliasId) { 128 if (singleValue && currentAliasId) {
126 - var entityId = scope.model[currentAliasId].entityId;  
127 - var entitiesInfo = scope.entityAliasesInfo[currentAliasId];  
128 - for (var i=0;i<entitiesInfo.length;i++) {  
129 - if (entitiesInfo[i].id === entityId) {  
130 - displayValue = entitiesInfo[i].name;  
131 - break;  
132 - }  
133 - } 129 + var aliasInfo = scope.aliasController.getInstantAliasInfo(currentAliasId);
  130 + displayValue = aliasInfo.currentEntity.name;
134 } else { 131 } else {
135 displayValue = $translate.instant('entity.entities'); 132 displayValue = $translate.instant('entity.entities');
136 } 133 }
@@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $ @@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
142 139
143 return { 140 return {
144 restrict: "E", 141 restrict: "E",
145 - require: "^ngModel",  
146 scope: { 142 scope: {
147 - entityAliasesInfo:'=' 143 + aliasController:'='
148 }, 144 },
149 link: linker 145 link: linker
150 }; 146 };
ui/src/app/entity/alias/aliases-entity-select.scss renamed from ui/src/app/entity/aliases-entity-select.scss