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 22 import org.springframework.web.bind.annotation.*;
23 23 import org.thingsboard.server.common.data.Customer;
24 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 26 import org.thingsboard.server.common.data.asset.Asset;
30 27 import org.thingsboard.server.common.data.id.*;
31 28 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -61,6 +58,19 @@ public class AlarmController extends BaseController {
61 58 }
62 59
63 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 74 @RequestMapping(value = "/alarm", method = RequestMethod.POST)
65 75 @ResponseBody
66 76 public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
... ... @@ -103,24 +113,31 @@ public class AlarmController extends BaseController {
103 113 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
104 114 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
105 115 @ResponseBody
106   - public TimePageData<Alarm> getAlarms(
  116 + public TimePageData<AlarmInfo> getAlarms(
107 117 @PathVariable("entityType") String strEntityType,
108 118 @PathVariable("entityId") String strEntityId,
  119 + @RequestParam(required = false) String searchStatus,
109 120 @RequestParam(required = false) String status,
110 121 @RequestParam int limit,
111 122 @RequestParam(required = false) Long startTime,
112 123 @RequestParam(required = false) Long endTime,
113 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 127 ) throws ThingsboardException {
116 128 checkParameter("EntityId", strEntityId);
117 129 checkParameter("EntityType", strEntityType);
118 130 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
  131 + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus);
119 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 137 checkEntityId(entityId);
121 138 try {
122 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 141 } catch (Exception e) {
125 142 throw handleException(e);
126 143 }
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.server.actors.service.ActorService;
25 25 import org.thingsboard.server.common.data.*;
26 26 import org.thingsboard.server.common.data.alarm.Alarm;
27 27 import org.thingsboard.server.common.data.alarm.AlarmId;
  28 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
28 29 import org.thingsboard.server.common.data.asset.Asset;
29 30 import org.thingsboard.server.common.data.id.*;
30 31 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -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 364 protected void checkAlarm(Alarm alarm) throws ThingsboardException {
353 365 checkNotNull(alarm);
354 366 checkTenantId(alarm.getTenantId());
... ...
... ... @@ -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 268 private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
254 269 RelationTypeGroup result = defaultValue;
255 270 if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
... ...
... ... @@ -53,6 +53,22 @@ public class Alarm extends BaseData<AlarmId> implements HasName {
53 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 72 @Override
57 73 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
58 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 32
33 33 private EntityId affectedEntityId;
34 34 private TimePageLink pageLink;
  35 + private AlarmSearchStatus searchStatus;
35 36 private AlarmStatus status;
  37 + private Boolean fetchOriginator;
36 38
37 39 }
... ...
1   -/*
  1 +/**
2 2 * Copyright © 2016-2017 The Thingsboard Authors
3 3 *
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
... ... @@ -14,18 +14,10 @@
14 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 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 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.alarm.Alarm;
  20 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
20 21 import org.thingsboard.server.common.data.alarm.AlarmQuery;
21 22 import org.thingsboard.server.common.data.id.EntityId;
22 23 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -36,5 +37,5 @@ public interface AlarmDao extends Dao<Alarm> {
36 37
37 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 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.alarm.Alarm;
20 20 import org.thingsboard.server.common.data.alarm.AlarmId;
  21 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
21 22 import org.thingsboard.server.common.data.alarm.AlarmQuery;
22 23 import org.thingsboard.server.common.data.page.TimePageData;
23 24
... ... @@ -34,6 +35,8 @@ public interface AlarmService {
34 35
35 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 17
18 18
19 19 import com.google.common.base.Function;
  20 +import com.google.common.util.concurrent.AsyncFunction;
20 21 import com.google.common.util.concurrent.Futures;
21 22 import com.google.common.util.concurrent.ListenableFuture;
22 23 import lombok.extern.slf4j.Slf4j;
... ... @@ -24,15 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired;
24 25 import org.springframework.stereotype.Service;
25 26 import org.springframework.util.StringUtils;
26 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 29 import org.thingsboard.server.common.data.id.EntityId;
32 30 import org.thingsboard.server.common.data.page.TimePageData;
33 31 import org.thingsboard.server.common.data.relation.EntityRelation;
34 32 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
35 33 import org.thingsboard.server.dao.entity.AbstractEntityService;
  34 +import org.thingsboard.server.dao.entity.EntityService;
36 35 import org.thingsboard.server.dao.exception.DataValidationException;
37 36 import org.thingsboard.server.dao.relation.EntityRelationsQuery;
38 37 import org.thingsboard.server.dao.relation.EntitySearchDirection;
... ... @@ -44,6 +43,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
44 43 import javax.annotation.Nullable;
45 44 import javax.annotation.PostConstruct;
46 45 import javax.annotation.PreDestroy;
  46 +import java.util.ArrayList;
47 47 import java.util.List;
48 48 import java.util.concurrent.ExecutionException;
49 49 import java.util.concurrent.ExecutorService;
... ... @@ -57,7 +57,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
57 57 public class BaseAlarmService extends AbstractEntityService implements AlarmService {
58 58
59 59 public static final String ALARM_RELATION_PREFIX = "ALARM_";
60   - public static final String ALARM_RELATION = "ALARM_ANY";
61 60
62 61 @Autowired
63 62 private AlarmDao alarmDao;
... ... @@ -68,6 +67,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
68 67 @Autowired
69 68 private RelationService relationService;
70 69
  70 + @Autowired
  71 + private EntityService entityService;
  72 +
71 73 protected ExecutorService readResultsProcessingExecutor;
72 74
73 75 @PostConstruct
... ... @@ -114,11 +116,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
114 116 query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
115 117 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
116 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 122 return saved;
123 123 }
124 124
... ... @@ -197,12 +197,44 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
197 197 }
198 198
199 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 235 @Nullable
204 236 @Override
205   - public TimePageData<Alarm> apply(@Nullable List<Alarm> alarms) {
  237 + public TimePageData<AlarmInfo> apply(@Nullable List<AlarmInfo> alarms) {
206 238 return new TimePageData<>(alarms, query.getPageLink());
207 239 }
208 240 });
... ... @@ -243,17 +275,45 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
243 275 query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
244 276 List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
245 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 281 } catch (ExecutionException | InterruptedException e) {
252 282 log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus);
253 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 317 private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) {
258 318 validateId(alarmId, "Alarm id should be specified!");
259 319 ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
17 17
18 18 import com.datastax.driver.core.querybuilder.QueryBuilder;
19 19 import com.datastax.driver.core.querybuilder.Select;
  20 +import com.google.common.base.Function;
20 21 import com.google.common.util.concurrent.AsyncFunction;
21 22 import com.google.common.util.concurrent.Futures;
22 23 import com.google.common.util.concurrent.ListenableFuture;
... ... @@ -26,7 +27,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26 27 import org.springframework.stereotype.Component;
27 28 import org.thingsboard.server.common.data.EntityType;
28 29 import org.thingsboard.server.common.data.alarm.Alarm;
  30 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
29 31 import org.thingsboard.server.common.data.alarm.AlarmQuery;
  32 +import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
30 33 import org.thingsboard.server.common.data.id.EntityId;
31 34 import org.thingsboard.server.common.data.id.TenantId;
32 35 import org.thingsboard.server.common.data.relation.EntityRelation;
... ... @@ -86,15 +89,25 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
86 89 }
87 90
88 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 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 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 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 112 return Futures.successfulAsList(alarmFutures);
100 113 });
... ...
... ... @@ -179,9 +179,14 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
179 179 eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()),
180 180 eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType),
181 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 190 pageLink, ModelConstants.RELATION_TO_ID_PROPERTY);
186 191 return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs));
187 192 }
... ...
... ... @@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
191 191
192 192 @Override
193 193 public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
194   - log.trace("Executing findByQuery [{}][{}]", query);
  194 + log.trace("Executing findByQuery [{}]", query);
195 195 RelationsSearchParameters params = query.getParameters();
196 196 final List<EntityTypeFilter> filters = query.getFilters();
197 197 if (filters == null || filters.isEmpty()) {
... ... @@ -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 251 protected void validate(EntityRelation relation) {
228 252 if (relation == null) {
229 253 throw new DataValidationException("Relation type should be specified!");
... ...
... ... @@ -52,6 +52,8 @@ public interface RelationService {
52 52
53 53 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
54 54
  55 + ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
  56 +
55 57 // TODO: This method may be useful for some validations in the future
56 58 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
57 59
... ...
... ... @@ -22,10 +22,7 @@ import org.junit.Before;
22 22 import org.junit.Test;
23 23 import org.thingsboard.server.common.data.EntityType;
24 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 26 import org.thingsboard.server.common.data.id.AssetId;
30 27 import org.thingsboard.server.common.data.id.DeviceId;
31 28 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -117,7 +114,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
117 114 Alarm created = alarmService.createOrUpdateAlarm(alarm);
118 115
119 116 // Check child relation
120   - TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder()
  117 + TimePageData<AlarmInfo> alarms = alarmService.findAlarms(AlarmQuery.builder()
121 118 .affectedEntityId(childId)
122 119 .status(AlarmStatus.ACTIVE_UNACK).pageLink(
123 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 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 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 28 aria-label="{{ 'action.view' | translate }}">
24 29 <md-tooltip md-direction="top">
25   - {{ 'action.view' | translate }}
  30 + {{ 'alarm.details' | translate }}
26 31 </md-tooltip>
27 32 <md-icon aria-label="{{ 'action.view' | translate }}"
28 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 21 function AlarmService($http, $q, $interval, $filter) {
22 22 var service = {
23 23 getAlarm: getAlarm,
  24 + getAlarmInfo: getAlarmInfo,
24 25 saveAlarm: saveAlarm,
25 26 ackAlarm: ackAlarm,
26 27 clearAlarm: clearAlarm,
... ... @@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) {
46 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 65 function saveAlarm(alarm, ignoreErrors, config) {
50 66 var deferred = $q.defer();
51 67 var url = '/api/alarm';
... ... @@ -91,7 +107,7 @@ function AlarmService($http, $q, $interval, $filter) {
91 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 111 var deferred = $q.defer();
96 112 var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit;
97 113
... ... @@ -104,9 +120,15 @@ function AlarmService($http, $q, $interval, $filter) {
104 120 if (angular.isDefined(pageLink.idOffset)) {
105 121 url += '&offset=' + pageLink.idOffset;
106 122 }
  123 + if (alarmSearchStatus) {
  124 + url += '&searchStatus=' + alarmSearchStatus;
  125 + }
107 126 if (alarmStatus) {
108 127 url += '&status=' + alarmStatus;
109 128 }
  129 + if (fetchOriginator) {
  130 + url += '&fetchOriginator=' + ((fetchOriginator===true) ? 'true' : 'false');
  131 + }
110 132 if (angular.isDefined(ascOrder) && ascOrder != null) {
111 133 url += '&ascOrder=' + (ascOrder ? 'true' : 'false');
112 134 }
... ... @@ -121,7 +143,8 @@ function AlarmService($http, $q, $interval, $filter) {
121 143
122 144 function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) {
123 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 148 function success(alarms) {
126 149 if (!alarmsList) {
127 150 alarmsList = [];
... ... @@ -171,7 +194,9 @@ function AlarmService($http, $q, $interval, $filter) {
171 194 var alarmsQuery = {
172 195 entityType: entityType,
173 196 entityId: entityId,
  197 + alarmSearchStatus: null,
174 198 alarmStatus: alarmStatus,
  199 + fetchOriginator: false,
175 200 interval: interval,
176 201 limit: limit,
177 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 +}
\ No newline at end of file
... ...
... ... @@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
30 30 findByTo: findByTo,
31 31 findInfoByTo: findInfoByTo,
32 32 findByToAndType: findByToAndType,
33   - findByQuery: findByQuery
  33 + findByQuery: findByQuery,
  34 + findInfoByQuery: findInfoByQuery
34 35 }
35 36
36 37 return service;
... ... @@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
159 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 27 getEntity: getEntity,
28 28 getEntities: getEntities,
29 29 getEntitiesByNameFilter: getEntitiesByNameFilter,
30   - processEntityAliases: processEntityAliases,
31   - getEntityKeys: getEntityKeys,
  30 + resolveAlias: resolveAlias,
  31 + resolveAliasFilter: resolveAliasFilter,
32 32 checkEntityAlias: checkEntityAlias,
33   - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
  33 + filterAliasByEntityTypes: filterAliasByEntityTypes,
  34 + getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
  35 + prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
  36 + getEntityKeys: getEntityKeys,
  37 + createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
34 38 getRelatedEntities: getRelatedEntities,
35 39 saveRelatedEntity: saveRelatedEntity,
36 40 getRelatedEntity: getRelatedEntity,
... ... @@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
173 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 228 function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
177 229 var promise;
178 230 var user = userService.getCurrentUser();
... ... @@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
193 245 }
194 246 break;
195 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 253 break;
198 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 260 break;
201 261 case types.entityType.rule:
202 262 promise = ruleService.getAllRules(pageLink);
... ... @@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
221 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 285 var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
228 286 if (promise) {
229 287 promise.then(
230 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 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 301 function fail() {
... ... @@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
241 305 } else {
242 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 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 354 var entitiesInfo = [];
253 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 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 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 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 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 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 720 return deferred.promise;
331 721 }
332 722
... ... @@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
354 744 }
355 745 }
356 746 deferred.resolve(result);
357   - }, function fail(response) {
358   - deferred.reject(response.data);
  747 + }, function fail() {
  748 + deferred.reject();
359 749 });
360 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 754 var deferred = $q.defer();
392 755 var datasources = [];
393 756 processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
... ... @@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
822 1185 }
823 1186 }
824 1187
825   -}
\ No newline at end of file
  1188 +}
... ...
... ... @@ -39,6 +39,9 @@ export default class Subscription {
39 39 this.cafs = {};
40 40 this.registrations = [];
41 41
  42 + var subscription = this;
  43 + var deferred = this.ctx.$q.defer();
  44 +
42 45 if (this.type === this.ctx.types.widgetType.rpc.value) {
43 46 this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
44 47 this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
... ... @@ -56,7 +59,11 @@ export default class Subscription {
56 59 this.rpcEnabled = false;
57 60 this.executingRpcRequest = false;
58 61 this.executingPromises = [];
59   - this.initRpc();
  62 + this.initRpc().then(
  63 + function() {
  64 + deferred.resolve(subscription);
  65 + }
  66 + );
60 67 } else {
61 68 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
62 69 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
... ... @@ -66,6 +73,15 @@ export default class Subscription {
66 73
67 74 this.datasources = this.ctx.utils.validateDatasources(options.datasources);
68 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 85 this.data = [];
70 86 this.hiddenData = [];
71 87 this.originalTimewindow = null;
... ... @@ -103,11 +119,41 @@ export default class Subscription {
103 119 this.legendConfig.showMax === true ||
104 120 this.legendConfig.showAvg === true ||
105 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 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 157 var dataIndex = 0;
112 158 for (var i = 0; i < this.datasources.length; i++) {
113 159 var datasource = this.datasources[i];
... ... @@ -199,21 +245,46 @@ export default class Subscription {
199 245 }
200 246
201 247 initRpc() {
  248 + var deferred = this.ctx.$q.defer();
202 249 if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
203 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 290 clearRpcError() {
... ... @@ -319,11 +390,11 @@ export default class Subscription {
319 390 this.onDataUpdated();
320 391 }
321 392
322   - onAliasesChanged() {
  393 + onAliasesChanged(aliasIds) {
323 394 if (this.type === this.ctx.types.widgetType.rpc.value) {
324   - this.checkRpcTarget();
  395 + return this.checkRpcTarget(aliasIds);
325 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 552 var datasource = this.datasources[i];
482 553 if (angular.isFunction(datasource))
483 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 556 var subscription = this;
519 557
... ... @@ -521,8 +559,8 @@ export default class Subscription {
521 559 subscriptionType: this.type,
522 560 subscriptionTimewindow: this.subscriptionTimewindow,
523 561 datasource: datasource,
524   - entityType: entityType,
525   - entityId: entityId,
  562 + entityType: datasource.entityType,
  563 + entityId: datasource.entityId,
526 564 dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
527 565 subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
528 566 },
... ... @@ -544,6 +582,10 @@ export default class Subscription {
544 582
545 583 this.datasourceListeners.push(listener);
546 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 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 611 var subscriptionsChanged = false;
578 612 for (var i = 0; i < this.datasourceListeners.length; i++) {
579 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 616 subscriptionsChanged = true;
594 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 624 destroy() {
... ... @@ -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 640 function calculateMin(data) {
644 641 if (data.length > 0) {
645 642 var result = Number(data[0][1]);
... ...
... ... @@ -48,11 +48,16 @@
48 48 disable-attribute-scope-selection="true">
49 49 </tb-attribute-table>
50 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 56 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}">
52 57 <tb-event-table flex entity-type="vm.types.entityType.asset"
53 58 entity-id="vm.grid.operatingItem().id.id"
54 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 61 </tb-event-table>
57 62 </md-tab>
58 63 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 import uiRouter from 'angular-ui-router';
17 17 import thingsboardGrid from '../components/grid.directive';
18   -import thingsboardEvent from '../event';
19 18 import thingsboardApiUser from '../api/user.service';
20 19 import thingsboardApiAsset from '../api/asset.service';
21 20 import thingsboardApiCustomer from '../api/customer.service';
... ... @@ -29,7 +28,6 @@ import AssetDirective from './asset.directive';
29 28 export default angular.module('thingsboard.asset', [
30 29 uiRouter,
31 30 thingsboardGrid,
32   - thingsboardEvent,
33 31 thingsboardApiUser,
34 32 thingsboardApiAsset,
35 33 thingsboardApiCustomer
... ...
... ... @@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
23 23
24 24 var service = {
25 25 validateAndUpdateDashboard: validateAndUpdateDashboard,
  26 + validateAndUpdateWidget: validateAndUpdateWidget,
26 27 getRootStateId: getRootStateId,
27 28 createSingleWidgetDashboard: createSingleWidgetDashboard,
  29 + createSingleEntityFilter: createSingleEntityFilter,
28 30 getStateLayoutsData: getStateLayoutsData,
29 31 createDefaultState: createDefaultState,
30 32 createDefaultLayoutData: createDefaultLayoutData,
... ... @@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
39 41
40 42 return service;
41 43
42   - function validateAndUpdateEntityAliases(configuration) {
  44 + function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
  45 + var aliasId, entityAlias;
43 46 if (angular.isUndefined(configuration.entityAliases)) {
44 47 configuration.entityAliases = {};
45 48 if (configuration.deviceAliases) {
46 49 var deviceAliases = configuration.deviceAliases;
47   - for (var aliasId in deviceAliases) {
  50 + for (aliasId in deviceAliases) {
48 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 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 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 142 function validateAndUpdateWidget(widget) {
77 143 if (!widget.config) {
78 144 widget.config = {};
... ... @@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
166 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 264 if (angular.isUndefined(dashboard.configuration.timewindow)) {
172 265 dashboard.configuration.timewindow = timeService.defaultTimewindow();
... ... @@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
243 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 348 function getStateLayoutsData(dashboard, targetState) {
247 349 var dashboardConfiguration = dashboard.configuration;
248 350 var states = dashboardConfiguration.states;
... ...
... ... @@ -65,6 +65,69 @@ export default angular.module('thingsboard.types', [])
65 65 clearedUnack: "CLEARED_UNACK",
66 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 131 position: {
69 132 top: {
70 133 value: "top",
... ... @@ -109,6 +172,62 @@ export default angular.module('thingsboard.types', [])
109 172 dashboard: "DASHBOARD",
110 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 231 entitySearchDirection: {
113 232 from: "FROM",
114 233 to: "TO"
... ... @@ -118,10 +237,6 @@ export default angular.module('thingsboard.types', [])
118 237 manages: "Manages"
119 238 },
120 239 eventType: {
121   - alarm: {
122   - value: "ALARM",
123   - name: "event.type-alarm"
124   - },
125 240 error: {
126 241 value: "ERROR",
127 242 name: "event.type-error"
... ...
... ... @@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
106 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 107 filterSearchTextEntities: filterSearchTextEntities,
108 108 guid: guid,
  109 + cleanCopy: cleanCopy,
109 110 isLocalUrl: isLocalUrl,
110 111 validateDatasources: validateDatasources,
111   - createKey: createKey,
112   - entityTypeName: entityTypeName
  112 + createKey: createKey
113 113 }
114 114
115 115 return service;
... ... @@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
291 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 304 function genNextColor(datasources) {
295 305 var index = 0;
296 306 if (datasources) {
... ... @@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
347 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 52 bindToController: {
53 53 widgets: '=',
54 54 widgetLayouts: '=?',
55   - aliasesInfo: '=',
  55 + aliasController: '=',
56 56 stateController: '=',
57 57 dashboardTimewindow: '=?',
58 58 columns: '=',
... ... @@ -85,7 +85,7 @@ function Dashboard() {
85 85 }
86 86
87 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 90 var highlightedMode = false;
91 91 var highlightedWidget = null;
... ... @@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
329 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 332 $scope.$on('gridster-resized', function (event, sizes, theGridster) {
337 333 if (checkIsLocalGridsterElement(theGridster)) {
338 334 vm.gridster = theGridster;
... ... @@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
796 792 }
797 793
798 794 function dashboardLoaded() {
799   - $timeout(function () {
  795 + $mdUtil.nextTick(function () {
800 796 if (vm.dashboardTimewindowWatch) {
801 797 vm.dashboardTimewindowWatch();
802 798 vm.dashboardTimewindowWatch = null;
... ... @@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
806 802 }, true);
807 803 adoptMaxRows();
808 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 828 function loading() {
... ...
... ... @@ -89,7 +89,7 @@
89 89 <div flex tb-widget
90 90 locals="{ visibleRect: vm.visibleRect,
91 91 widget: widget,
92   - aliasesInfo: vm.aliasesInfo,
  92 + aliasController: vm.aliasController,
93 93 stateController: vm.stateController,
94 94 isEdit: vm.isEdit,
95 95 stDiff: vm.stDiff,
... ...
... ... @@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
20 20 .name;
21 21
22 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 25 var vm = this;
26 26
27 27 vm.dataKey = dataKey;
28 28 vm.dataKeySettingsSchema = dataKeySettingsSchema;
29 29 vm.entityAlias = entityAlias;
30   - vm.entityAliases = entityAliases;
  30 + vm.aliasController = aliasController;
31 31
32 32 vm.hide = function () {
33 33 $mdDialog.hide();
... ... @@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
38 38 };
39 39
40 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 65 vm.save = function () {
... ...
... ... @@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
103 103 ngModelCtrl.$render = function () {
104 104 if (ngModelCtrl.$viewValue) {
105 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 109 } else {
111 110 scope.entityAlias = null;
112 111 }
... ... @@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
182 181 dataKey: angular.copy(dataKey),
183 182 dataKeySettingsSchema: scope.datakeySettingsSchema,
184 183 entityAlias: scope.entityAlias,
185   - entityAliases: scope.entityAliases
  184 + aliasController: scope.aliasController
186 185 },
187 186 parent: angular.element($document[0].body),
188 187 fullscreen: true,
... ... @@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
236 235 require: "^ngModel",
237 236 scope: {
238 237 widgetType: '=',
239   - entityAliases: '=',
  238 + aliasController: '=',
240 239 datakeySettingsSchema: '=',
241 240 generateDataKey: '&',
242 241 fetchEntityKeys: '&',
... ...
... ... @@ -18,7 +18,7 @@
18 18 <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
19 19 <tb-entity-alias-select
20 20 tb-required="true"
21   - entity-aliases="entityAliases"
  21 + alias-controller="aliasController"
22 22 ng-model="entityAlias"
23 23 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
24 24 </tb-entity-alias-select>
... ...
... ... @@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
71 71 }
72 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 81 ngModelCtrl.$render = function () {
75 82 if (ngModelCtrl.$viewValue) {
76 83 var funcDataKeys = [];
... ... @@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
78 85 funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
79 86 }
80 87 scope.funcDataKeys = funcDataKeys;
  88 + scope.datasourceName = ngModelCtrl.$viewValue.name;
81 89 }
82 90 };
83 91
... ...
... ... @@ -15,23 +15,29 @@
15 15 */
16 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   -}
\ No newline at end of file
  43 +}
... ...
... ... @@ -15,59 +15,68 @@
15 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 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 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 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 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 82 </section>
... ...
... ... @@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
76 76 restrict: "E",
77 77 require: "^ngModel",
78 78 scope: {
79   - entityAliases: '=',
  79 + aliasController: '=',
80 80 widgetType: '=',
81 81 functionsOnly: '=',
82 82 datakeySettingsSchema: '=',
... ...
... ... @@ -37,7 +37,7 @@
37 37 ng-switch-when="entity"
38 38 ng-required="model.type === types.datasourceType.entity"
39 39 widget-type="widgetType"
40   - entity-aliases="entityAliases"
  40 + alias-controller="aliasController"
41 41 generate-data-key="generateDataKey({chip: chip, type: type})"
42 42 fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
43 43 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
... ...
... ... @@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
31 31 .name;
32 32
33 33 /*@ngInject*/
34   -function EntityAliasSelect($compile, $templateCache, $mdConstant) {
  34 +function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
35 35
36 36 var linker = function (scope, element, attrs, ngModelCtrl) {
37 37 var template = $templateCache.get(entityAliasSelectTemplate);
... ... @@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
49 49 ngModelCtrl.$setValidity('entityAlias', valid);
50 50 };
51 51
52   - scope.$watch('entityAliases', function () {
  52 + scope.$watch('aliasController', function () {
53 53 scope.entityAliasList = [];
54   - for (var aliasId in scope.entityAliases) {
  54 + var entityAliases = scope.aliasController.getEntityAliases();
  55 + for (var aliasId in entityAliases) {
55 56 if (scope.allowedEntityTypes) {
56   - if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) {
  57 + if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
57 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 65 scope.$watch('entityAlias', function () {
67 66 scope.updateView();
... ... @@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
141 140 link: linker,
142 141 scope: {
143 142 tbRequired: '=?',
144   - entityAliases: '=',
  143 + aliasController: '=',
145 144 allowedEntityTypes: '=?',
146 145 onCreateEntityAlias: '&'
147 146 }
... ...
... ... @@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
128 128 } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
129 129 if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
130 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 134 } else {
139 135 scope.targetDeviceAlias.value = null;
140 136 }
... ... @@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
395 391 widgetType: '=',
396 392 widgetSettingsSchema: '=',
397 393 datakeySettingsSchema: '=',
398   - entityAliases: '=',
  394 + aliasController: '=',
399 395 functionsOnly: '=',
400 396 fetchEntityKeys: '&',
401 397 onCreateEntityAlias: '&',
... ...
... ... @@ -60,7 +60,7 @@
60 60 style="padding: 0 0 0 10px; margin: 5px;">
61 61 <tb-datasource flex ng-model="datasource.value"
62 62 widget-type="widgetType"
63   - entity-aliases="entityAliases"
  63 + alias-controller="aliasController"
64 64 functions-only="functionsOnly"
65 65 datakey-settings-schema="datakeySettingsSchema"
66 66 generate-data-key="generateDataKey(chip,type)"
... ... @@ -104,7 +104,7 @@
104 104 <v-pane-content style="padding: 0 5px;">
105 105 <tb-entity-alias-select flex
106 106 tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
107   - entity-aliases="entityAliases"
  107 + alias-controller="aliasController"
108 108 allowed-entity-types="[types.entityType.device]"
109 109 ng-model="targetDeviceAlias.value"
110 110 on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">
... ...
... ... @@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
20 20 /* eslint-disable angular/angularelement */
21 21
22 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 24 datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
25   - dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) {
  25 + dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
26 26
27 27 var vm = this;
28 28
... ... @@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
37 37 $scope.executingRpcRequest = false;
38 38
39 39 var gridsterItemInited = false;
  40 + var subscriptionInited = false;
  41 + var widgetSizeDetected = false;
40 42
41 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 45 var widgetContext = {
53 46 inited: false,
54 47 $scope: $scope,
55   - $container: $('#container', $element),
56   - $containerParent: $($element),
  48 + $container: null,
  49 + $containerParent: null,
57 50 width: 0,
58 51 height: 0,
59 52 isEdit: isEdit,
... ... @@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
80 73 createSubscription: function(options, subscribe) {
81 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 76 createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
108 77 return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
109 78 },
... ... @@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
149 118 dashboardTimewindowApi: dashboardTimewindowApi,
150 119 types: types,
151 120 stDiff: stDiff,
152   - aliasesInfo: aliasesInfo
  121 + aliasController: aliasController
153 122 };
154 123
155 124 var widgetTypeInstance;
... ... @@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
203 172
204 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 181 function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
225 182 var deferred = $q.defer();
... ... @@ -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 194 function (datasources) {
238 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 209 return deferred.promise;
247 210 }
248 211
249 212 function createSubscription(options, subscribe) {
  213 + var deferred = $q.defer();
250 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 231 function defaultComponentsOptions(options) {
... ... @@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
310 281 }
311 282
312 283 function createDefaultSubscription() {
313   - var subscription;
314 284 var options;
  285 + var deferred = $q.defer();
315 286 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
316 287 options = {
317 288 type: widget.type,
... ... @@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
319 290 };
320 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 311 } else if (widget.type === types.widgetType.rpc.value) {
334 312 $scope.loadingData = false;
... ... @@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
356 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 346 } else if (widget.type === types.widgetType.static.value) {
361 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 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 453 $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
378 454 onEditModeChanged(isEdit);
379 455 });
380 456
381   - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
382   -
383 457 $scope.$watch(function () {
384 458 return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
385 459 }, function () {
... ... @@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
398 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 477 for (var id in widgetContext.subscriptions) {
404 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 486 $scope.$on("$destroy", function () {
410   - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
411 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 531 function handleWidgetException(e) {
... ... @@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
417 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 545 widgetContext.inited = true;
423 546 try {
424 547 widgetTypeInstance.onInit();
... ... @@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
443 566 widgetContext.width = width;
444 567 widgetContext.height = height;
445 568 sizeChanged = true;
  569 + widgetSizeDetected = true;
446 570 }
447 571 }
448 572 return sizeChanged;
... ... @@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
462 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 596 if (item && item.gridster) {
473 597 widgetContext.isMobile = item.gridster.isMobile;
474 598 gridsterItemInited = true;
475   - if (checkSize()) {
476   - onInit();
477   - }
  599 + onInit();
478 600 // gridsterItemElement = $(item.$element);
479 601 //updateVisibility();
480 602 }
... ... @@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
544 666 var subscription = widgetContext.subscriptions[id];
545 667 subscription.destroy();
546 668 }
  669 + subscriptionInited = false;
547 670 widgetContext.subscriptions = [];
548 671 if (widgetContext.inited) {
549 672 widgetContext.inited = false;
... ... @@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
559 682 handleWidgetException(e);
560 683 }
561 684 }
  685 + destroyWidgetElement();
562 686 }
563 687
564 688 //TODO: widgets visibility
... ...
... ... @@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
28 28 .name;
29 29
30 30 /*@ngInject*/
31   -function Widget($controller, $compile, types, widgetService) {
  31 +function Widget($controller, widgetService) {
32 32 return {
33 33 scope: true,
34 34 link: function (scope, elem, attrs) {
... ... @@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
81 81
82 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 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 88 widgetController = $controller('WidgetController', locals);
170 89
... ...
... ... @@ -48,11 +48,16 @@
48 48 disable-attribute-scope-selection="true">
49 49 </tb-attribute-table>
50 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 56 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}">
52 57 <tb-event-table flex entity-type="vm.types.entityType.customer"
53 58 entity-id="vm.grid.operatingItem().id.id"
54 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 61 </tb-event-table>
57 62 </md-tab>
58 63 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
... ...
... ... @@ -15,17 +15,18 @@
15 15 */
16 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 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 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 26 var vm = this;
26 27
27 28 vm.dashboard = dashboard;
28   - vm.aliasesInfo = aliasesInfo;
  29 + vm.aliasController = aliasController;
29 30 vm.widget = widget;
30 31 vm.widgetInfo = widgetInfo;
31 32
... ... @@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
85 86 }
86 87
87 88 function cancel () {
88   - $mdDialog.cancel({aliasesInfo: vm.aliasesInfo});
  89 + $mdDialog.cancel();
89 90 }
90 91
91 92 function add () {
... ... @@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
94 95 vm.widget.config = vm.widgetConfig.config;
95 96 vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
96 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 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 127 function createEntityAlias (event, alias, allowedEntityTypes) {
111 128
112 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 132 $mdDialog.show({
116   - controller: 'EntityAliasesController',
  133 + controller: 'EntityAliasDialogController',
117 134 controllerAs: 'vm',
118   - templateUrl: entityAliasesTemplate,
  135 + templateUrl: entityAliasDialogTemplate,
119 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 142 parent: angular.element($document[0].body),
129 143 fullscreen: true,
130 144 skipHide: true,
131 145 targetEvent: event
132 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 150 }, function () {
144 151 deferred.reject();
145 152 });
... ...
... ... @@ -37,7 +37,7 @@
37 37 ng-model="vm.widgetConfig"
38 38 widget-settings-schema="vm.settingsSchema"
39 39 datakey-settings-schema="vm.dataKeySettingsSchema"
40   - entity-aliases="vm.aliasesInfo.entityAliases"
  40 + alias-controller="vm.aliasController"
41 41 functions-only="vm.functionsOnly"
42 42 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
43 43 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 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 19 import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
20 20 import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
21 21 import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
... ... @@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
24 24
25 25 /* eslint-enable import/no-unresolved, import/default */
26 26
  27 +import AliasController from '../api/alias-controller';
  28 +
27 29 /*@ngInject*/
28   -export default function DashboardController(types, dashboardUtils, widgetService, userService,
  30 +export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
29 31 dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
30 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 344 vm.dashboardConfiguration = vm.dashboard.configuration;
343 345 vm.dashboardCtx.dashboard = vm.dashboard;
344 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 349 var parentScope = $window.parent.angular.element($window.frameElement).scope();
346 350 parentScope.$root.$broadcast('widgetEditModeInited');
347 351 parentScope.$root.$apply();
... ... @@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
349 353 dashboardService.getDashboard($stateParams.dashboardId)
350 354 .then(function success(dashboard) {
351 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 363 .then(
354 364 function(resolution) {
355 365 if (resolution.error && !isTenantAdmin()) {
... ... @@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
362 372 vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
363 373 }
364 374 }
365   - );
  375 + );*/
366 376 }, function fail() {
367 377 vm.configurationError = true;
368 378 });
... ... @@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
373 383 var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
374 384 if (layoutsData) {
375 385 vm.dashboardCtx.state = state;
  386 + vm.dashboardCtx.aliasController.dashboardStateChanged();
376 387 var layoutVisibilityChanged = false;
377 388 for (var l in vm.layouts) {
378 389 var layout = vm.layouts[l];
... ... @@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
916 927 templateUrl: addWidgetTemplate,
917 928 locals: {
918 929 dashboard: vm.dashboard,
919   - aliasesInfo: vm.dashboardCtx.aliasesInfo,
  930 + aliasController: vm.dashboardCtx.aliasController,
920 931 widget: newWidget,
921 932 widgetInfo: widgetTypeInfo
922 933 },
... ... @@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
930 941 }
931 942 }).then(function (result) {
932 943 var widget = result.widget;
933   - vm.dashboardCtx.aliasesInfo = result.aliasesInfo;
934 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 1034 notifyDashboardUpdated();
1026 1035 }
1027 1036
1028   - function showAliasesResolutionError(error) {
  1037 +/* function showAliasesResolutionError(error) {
1029 1038 var alert = $mdDialog.alert()
1030 1039 .parent(angular.element($document[0].body))
1031 1040 .clickOutsideToClose(true)
... ... @@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
1037 1046 alert._options.fullscreen = true;
1038 1047
1039 1048 $mdDialog.show(alert);
1040   - }
  1049 + }*/
1041 1050
1042 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 1055 function notifyDashboardUpdated() {
... ...
... ... @@ -57,8 +57,7 @@
57 57 </tb-timewindow>
58 58 <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
59 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 61 </tb-aliases-entity-select>
63 62 <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
64 63 ng-click="vm.openEntityAliases($event)">
... ... @@ -179,7 +178,7 @@
179 178 <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
180 179 <tb-edit-widget
181 180 dashboard="vm.dashboard"
182   - aliases-info="vm.dashboardCtx.aliasesInfo"
  181 + alias-controller="vm.dashboardCtx.aliasController"
183 182 widget="vm.editingWidget"
184 183 widget-layout="vm.editingWidgetLayout"
185 184 the-form="vm.widgetForm">
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 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 import editWidgetTemplate from './edit-widget.tpl.html';
20 20
21 21 /* eslint-enable import/no-unresolved, import/default */
... ... @@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
68 68 });
69 69
70 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 95 scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
80 96
81 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 100 $mdDialog.show({
85   - controller: 'EntityAliasesController',
  101 + controller: 'EntityAliasDialogController',
86 102 controllerAs: 'vm',
87   - templateUrl: entityAliasesTemplate,
  103 + templateUrl: entityAliasDialogTemplate,
88 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 110 parent: angular.element($document[0].body),
98 111 fullscreen: true,
99 112 skipHide: true,
100 113 targetEvent: event
101 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 118 }, function () {
113 119 deferred.reject();
114 120 });
... ... @@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
124 130 link: linker,
125 131 scope: {
126 132 dashboard: '=',
127   - aliasesInfo: '=',
  133 + aliasController: '=',
128 134 widget: '=',
129 135 widgetLayout: '=',
130 136 theForm: '='
... ...
... ... @@ -21,7 +21,7 @@
21 21 is-data-enabled="isDataEnabled"
22 22 widget-settings-schema="settingsSchema"
23 23 datakey-settings-schema="dataKeySettingsSchema"
24   - entity-aliases="aliasesInfo.entityAliases"
  24 + alias-controller="aliasController"
25 25 functions-only="functionsOnly"
26 26 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
27 27 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
... ...
... ... @@ -45,7 +45,7 @@
45 45 widget-layouts="vm.layoutCtx.widgetLayouts"
46 46 columns="vm.layoutCtx.gridSettings.columns"
47 47 margins="vm.layoutCtx.gridSettings.margins"
48   - aliases-info="vm.dashboardCtx.aliasesInfo"
  48 + alias-controller="vm.dashboardCtx.aliasController"
49 49 state-controller="vm.dashboardCtx.stateController"
50 50 dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
51 51 is-edit="vm.isEdit"
... ...
... ... @@ -49,11 +49,16 @@
49 49 disable-attribute-scope-selection="true">
50 50 </tb-attribute-table>
51 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 57 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
53 58 <tb-event-table flex entity-type="vm.types.entityType.device"
54 59 entity-id="vm.grid.operatingItem().id.id"
55 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 62 </tb-event-table>
58 63 </md-tab>
59 64 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 import uiRouter from 'angular-ui-router';
17 17 import thingsboardGrid from '../components/grid.directive';
18   -import thingsboardEvent from '../event';
19 18 import thingsboardApiUser from '../api/user.service';
20 19 import thingsboardApiDevice from '../api/device.service';
21 20 import thingsboardApiCustomer from '../api/customer.service';
... ... @@ -30,7 +29,6 @@ import DeviceDirective from './device.directive';
30 29 export default angular.module('thingsboard.device', [
31 30 uiRouter,
32 31 thingsboardGrid,
33   - thingsboardEvent,
34 32 thingsboardApiUser,
35 33 thingsboardApiDevice,
36 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 18 <md-content flex layout="column">
19 19 <section flex layout="column">
20 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 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 27 </md-option>
28 28 </md-select>
29 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 29 /*@ngInject*/
30 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 34 /* tbAliasesEntitySelect (ng-model)
35 35 * {
... ... @@ -81,10 +81,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
81 81 position: position,
82 82 fullscreen: false,
83 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 86 scope.updateView();
89 87 }
90 88 },
... ... @@ -96,41 +94,40 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
96 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 105 scope.updateView = function () {
100   - var value = angular.copy(scope.model);
101   - ngModelCtrl.$setViewValue(value);
102 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 109 function updateDisplayValue() {
114 110 var displayValue;
115 111 var singleValue = true;
116 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 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 131 } else {
135 132 displayValue = $translate.instant('entity.entities');
136 133 }
... ... @@ -142,9 +139,8 @@ export default function AliasesEntitySelectDirective($compile, $templateCache, $
142 139
143 140 return {
144 141 restrict: "E",
145   - require: "^ngModel",
146 142 scope: {
147   - entityAliasesInfo:'='
  143 + aliasController:'='
148 144 },
149 145 link: linker
150 146 };
... ...
ui/src/app/entity/alias/aliases-entity-select.scss renamed from ui/src/app/entity/aliases-entity-select.scss