thingsboard-memoizeit
Changes
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java 15(+15 -0)
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmSearchStatus.java 18(+5 -13)
ui/src/app/alarm/alarm.scss 78(+78 -0)
ui/src/app/alarm/alarm-details-dialog.controller.js 134(+134 -0)
ui/src/app/alarm/alarm-details-dialog.scss 27(+27 -0)
ui/src/app/alarm/alarm-details-dialog.tpl.html 107(+107 -0)
ui/src/app/alarm/alarm-header.directive.js 39(+39 -0)
ui/src/app/alarm/alarm-row.directive.js 67(+67 -0)
ui/src/app/alarm/alarm-row.tpl.html 17(+11 -6)
ui/src/app/alarm/alarm-table.directive.js 211(+211 -0)
ui/src/app/alarm/alarm-table.tpl.html 49(+49 -0)
ui/src/app/alarm/index.js 27(+27 -0)
ui/src/app/api/alarm.service.js 29(+27 -2)
ui/src/app/api/alias-controller.js 279(+279 -0)
ui/src/app/api/entity.service.js 563(+463 -100)
ui/src/app/api/entity-relation.service.js 14(+13 -1)
ui/src/app/api/subscription.js 207(+102 -105)
ui/src/app/asset/assets.tpl.html 7(+6 -1)
ui/src/app/asset/index.js 2(+0 -2)
ui/src/app/common/dashboard-utils.service.js 148(+125 -23)
ui/src/app/common/types.constant.js 123(+119 -4)
ui/src/app/common/utils.service.js 37(+12 -25)
ui/src/app/components/dashboard.directive.js 37(+23 -14)
ui/src/app/components/datasource-func.scss 38(+22 -16)
ui/src/app/components/datasource-func.tpl.html 111(+60 -51)
ui/src/app/components/widget.controller.js 320(+222 -98)
ui/src/app/components/widget.directive.js 85(+2 -83)
ui/src/app/dashboard/add-widget.controller.js 69(+38 -31)
ui/src/app/dashboard/dashboard.controller.js 41(+20 -21)
ui/src/app/dashboard/edit-widget.directive.js 62(+34 -28)
ui/src/app/device/devices.tpl.html 7(+6 -1)
ui/src/app/device/index.js 2(+0 -2)
ui/src/app/entity/alias/entity-aliases.controller.js 152(+71 -81)
ui/src/app/entity/alias/entity-aliases.scss 49(+49 -0)
ui/src/app/entity/alias/entity-aliases.tpl.html 121(+60 -61)
ui/src/app/entity/entity-filter.directive.js 232(+63 -169)
ui/src/app/entity/entity-filter.scss 31(+11 -20)
ui/src/app/entity/entity-filter.tpl.html 262(+214 -48)
ui/src/app/entity/entity-filter-view.directive.js 205(+205 -0)
ui/src/app/entity/entity-filter-view.scss 33(+33 -0)
ui/src/app/entity/entity-list.directive.js 130(+130 -0)
ui/src/app/entity/entity-list.scss 30(+30 -0)
ui/src/app/entity/entity-list.tpl.html 52(+52 -0)
ui/src/app/entity/entity-subtype-list.directive.js 146(+146 -0)
ui/src/app/entity/entity-subtype-list.scss 30(+30 -0)
ui/src/app/entity/entity-type-list.directive.js 111(+111 -0)
ui/src/app/entity/entity-type-list.scss 30(+30 -0)
ui/src/app/entity/entity-type-list.tpl.html 54(+54 -0)
ui/src/app/entity/index.js 18(+15 -3)
ui/src/app/event/event.scss 4(+2 -2)
ui/src/app/layout/index.js 4(+4 -0)
ui/src/app/locale/locale.constant.js 149(+137 -12)
ui/src/app/plugin/index.js 2(+0 -2)
ui/src/app/plugin/plugins.tpl.html 8(+6 -2)
ui/src/app/rule/index.js 2(+0 -2)
ui/src/app/rule/rules.tpl.html 8(+6 -2)
ui/src/app/services/item-buffer.service.js 37(+12 -25)
ui/src/app/tenant/tenants.tpl.html 7(+6 -1)
ui/src/scss/main.scss 27(+27 -0)
Details
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index 7a2c273..19c75e7 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -22,10 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Event;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmId;
-import org.thingsboard.server.common.data.alarm.AlarmQuery;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.*;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageData;
@@ -61,6 +58,19 @@ public class AlarmController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET)
+ @ResponseBody
+ public AlarmInfo getAlarmInfoById(@PathVariable("alarmId") String strAlarmId) throws ThingsboardException {
+ checkParameter("alarmId", strAlarmId);
+ try {
+ AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
+ return checkAlarmInfoId(alarmId);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm", method = RequestMethod.POST)
@ResponseBody
public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
@@ -103,24 +113,31 @@ public class AlarmController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody
- public TimePageData<Alarm> getAlarms(
+ public TimePageData<AlarmInfo> getAlarms(
@PathVariable("entityType") String strEntityType,
@PathVariable("entityId") String strEntityId,
+ @RequestParam(required = false) String searchStatus,
@RequestParam(required = false) String status,
@RequestParam int limit,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
- @RequestParam(required = false) String offset
+ @RequestParam(required = false) String offset,
+ @RequestParam(required = false) Boolean fetchOriginator
) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
+ AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus);
AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
+ if (alarmSearchStatus != null && alarmStatus != null) {
+ throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
+ "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+ }
checkEntityId(entityId);
try {
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
- return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmStatus)).get());
+ return checkNotNull(alarmService.findAlarms(new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 1feef4a..1040f3a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -25,6 +25,7 @@ import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink;
@@ -349,6 +350,17 @@ public abstract class BaseController {
}
}
+ AlarmInfo checkAlarmInfoId(AlarmId alarmId) throws ThingsboardException {
+ try {
+ validateId(alarmId, "Incorrect alarmId " + alarmId);
+ AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(alarmId).get();
+ checkAlarm(alarmInfo);
+ return alarmInfo;
+ } catch (Exception e) {
+ throw handleException(e, false);
+ }
+ }
+
protected void checkAlarm(Alarm alarm) throws ThingsboardException {
checkNotNull(alarm);
checkTenantId(alarm.getTenantId());
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
index 3ddc597..4aa1a0a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -250,6 +250,21 @@ public class EntityRelationController extends BaseController {
}
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
+ @ResponseBody
+ public List<EntityRelationInfo> findInfoByQuery(@RequestBody EntityRelationsQuery query) throws ThingsboardException {
+ checkNotNull(query);
+ checkNotNull(query.getParameters());
+ checkNotNull(query.getFilters());
+ checkEntityId(query.getParameters().getEntityId());
+ try {
+ return checkNotNull(relationService.findInfoByQuery(query).get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
RelationTypeGroup result = defaultValue;
if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
index 09695e5..e48cf5b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
@@ -53,6 +53,22 @@ public class Alarm extends BaseData<AlarmId> implements HasName {
super(id);
}
+ public Alarm(Alarm alarm) {
+ super(alarm.getId());
+ this.createdTime = alarm.getCreatedTime();
+ this.tenantId = alarm.getTenantId();
+ this.type = alarm.getType();
+ this.originator = alarm.getOriginator();
+ this.severity = alarm.getSeverity();
+ this.status = alarm.getStatus();
+ this.startTs = alarm.getStartTs();
+ this.endTs = alarm.getEndTs();
+ this.ackTs = alarm.getAckTs();
+ this.clearTs = alarm.getClearTs();
+ this.details = alarm.getDetails();
+ this.propagate = alarm.isPropagate();
+ }
+
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
new file mode 100644
index 0000000..ef24f1c
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.alarm;
+
+public class AlarmInfo extends Alarm {
+
+ private static final long serialVersionUID = 2807343093519543363L;
+
+ private String originatorName;
+
+ public AlarmInfo() {
+ super();
+ }
+
+ public AlarmInfo(Alarm alarm) {
+ super(alarm);
+ }
+
+ public String getOriginatorName() {
+ return originatorName;
+ }
+
+ public void setOriginatorName(String originatorName) {
+ this.originatorName = originatorName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ AlarmInfo alarmInfo = (AlarmInfo) o;
+
+ return originatorName != null ? originatorName.equals(alarmInfo.originatorName) : alarmInfo.originatorName == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (originatorName != null ? originatorName.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
index 00ca6c3..55019c8 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
@@ -32,6 +32,8 @@ public class AlarmQuery {
private EntityId affectedEntityId;
private TimePageLink pageLink;
+ private AlarmSearchStatus searchStatus;
private AlarmStatus status;
+ private Boolean fetchOriginator;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java
index 0f1b346..a8704b2 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatus.java
@@ -30,4 +30,13 @@ public enum AlarmStatus {
return this == CLEARED_ACK || this == CLEARED_UNACK;
}
+ public AlarmSearchStatus getClearSearchStatus() {
+ return this.isCleared() ? AlarmSearchStatus.CLEARED : AlarmSearchStatus.ACTIVE;
+ }
+
+ public AlarmSearchStatus getAckSearchStatus() {
+ return this.isAck() ? AlarmSearchStatus.ACK : AlarmSearchStatus.UNACK;
+ }
+
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
index 712bbd1..0ab7620 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -36,5 +37,5 @@ public interface AlarmDao extends Dao<Alarm> {
Alarm save(Alarm alarm);
- ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query);
+ ListenableFuture<List<AlarmInfo>> findAlarms(AlarmQuery query);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
index 5399d9d..3556d51 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.page.TimePageData;
@@ -34,6 +35,8 @@ public interface AlarmService {
ListenableFuture<Alarm> findAlarmByIdAsync(AlarmId alarmId);
- ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query);
+ ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId);
+
+ ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index 262a11b..94f8fdb 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
import com.google.common.base.Function;
+import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
@@ -24,15 +25,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmId;
-import org.thingsboard.server.common.data.alarm.AlarmQuery;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.*;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.entity.AbstractEntityService;
+import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.EntityRelationsQuery;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
@@ -44,6 +43,7 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -57,7 +57,6 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
public class BaseAlarmService extends AbstractEntityService implements AlarmService {
public static final String ALARM_RELATION_PREFIX = "ALARM_";
- public static final String ALARM_RELATION = "ALARM_ANY";
@Autowired
private AlarmDao alarmDao;
@@ -68,6 +67,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Autowired
private RelationService relationService;
+ @Autowired
+ private EntityService entityService;
+
protected ExecutorService readResultsProcessingExecutor;
@PostConstruct
@@ -114,11 +116,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
query.setParameters(new RelationsSearchParameters(saved.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
for (EntityId parentId : parentEntities) {
- createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));
- createRelation(new EntityRelation(parentId, saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM));
+ createAlarmRelation(parentId, saved.getId(), saved.getStatus(), true);
}
- createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION, RelationTypeGroup.ALARM));
- createRelation(new EntityRelation(alarm.getOriginator(), saved.getId(), ALARM_RELATION_PREFIX + saved.getStatus().name(), RelationTypeGroup.ALARM));
+ createAlarmRelation(alarm.getOriginator(), saved.getId(), saved.getStatus(), true);
return saved;
}
@@ -197,12 +197,44 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
@Override
- public ListenableFuture<TimePageData<Alarm>> findAlarms(AlarmQuery query) {
- ListenableFuture<List<Alarm>> alarms = alarmDao.findAlarms(query);
- return Futures.transform(alarms, new Function<List<Alarm>, TimePageData<Alarm>>() {
+ public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(AlarmId alarmId) {
+ log.trace("Executing findAlarmInfoByIdAsync [{}]", alarmId);
+ validateId(alarmId, "Incorrect alarmId " + alarmId);
+ return Futures.transform(alarmDao.findAlarmByIdAsync(alarmId.getId()),
+ (AsyncFunction<Alarm, AlarmInfo>) alarm1 -> {
+ AlarmInfo alarmInfo = new AlarmInfo(alarm1);
+ return Futures.transform(
+ entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function<String, AlarmInfo>)
+ originatorName -> {
+ alarmInfo.setOriginatorName(originatorName);
+ return alarmInfo;
+ }
+ );
+ });
+ }
+
+ @Override
+ public ListenableFuture<TimePageData<AlarmInfo>> findAlarms(AlarmQuery query) {
+ ListenableFuture<List<AlarmInfo>> alarms = alarmDao.findAlarms(query);
+ if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
+ alarms = Futures.transform(alarms, (AsyncFunction<List<AlarmInfo>, List<AlarmInfo>>) input -> {
+ List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());
+ for (AlarmInfo alarmInfo : input) {
+ alarmFutures.add(Futures.transform(
+ entityService.fetchEntityNameAsync(alarmInfo.getOriginator()), (Function<String, AlarmInfo>)
+ originatorName -> {
+ alarmInfo.setOriginatorName(originatorName);
+ return alarmInfo;
+ }
+ ));
+ }
+ return Futures.successfulAsList(alarmFutures);
+ });
+ }
+ return Futures.transform(alarms, new Function<List<AlarmInfo>, TimePageData<AlarmInfo>>() {
@Nullable
@Override
- public TimePageData<Alarm> apply(@Nullable List<Alarm> alarms) {
+ public TimePageData<AlarmInfo> apply(@Nullable List<AlarmInfo> alarms) {
return new TimePageData<>(alarms, query.getPageLink());
}
});
@@ -243,17 +275,45 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(query).get().stream().map(r -> r.getFrom()).collect(Collectors.toList());
for (EntityId parentId : parentEntities) {
- deleteRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));
- createRelation(new EntityRelation(parentId, alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM));
- }
- deleteRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + oldStatus.name(), RelationTypeGroup.ALARM));
- createRelation(new EntityRelation(alarm.getOriginator(), alarm.getId(), ALARM_RELATION_PREFIX + newStatus.name(), RelationTypeGroup.ALARM));
+ updateAlarmRelation(parentId, alarm.getId(), oldStatus, newStatus);
+ }
+ updateAlarmRelation(alarm.getOriginator(), alarm.getId(), oldStatus, newStatus);
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to update relations. Old status: [{}], New status: [{}]", alarm.getId(), oldStatus, newStatus);
throw new RuntimeException(e);
}
}
+ private void createAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status, boolean createAnyRelation) {
+ try {
+ if (createAnyRelation) {
+ createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM));
+ }
+ createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM));
+ createRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
+ } catch (ExecutionException | InterruptedException e) {
+ log.warn("[{}] Failed to create relation. Status: [{}]", alarmId, status);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void deleteAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus status) {
+ try {
+ deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.name(), RelationTypeGroup.ALARM));
+ deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getClearSearchStatus().name(), RelationTypeGroup.ALARM));
+ deleteRelation(new EntityRelation(entityId, alarmId, ALARM_RELATION_PREFIX + status.getAckSearchStatus().name(), RelationTypeGroup.ALARM));
+ } catch (ExecutionException | InterruptedException e) {
+ log.warn("[{}] Failed to delete relation. Status: [{}]", alarmId, status);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void updateAlarmRelation(EntityId entityId, EntityId alarmId, AlarmStatus oldStatus, AlarmStatus newStatus) {
+ deleteAlarmRelation(entityId, alarmId, oldStatus);
+ createAlarmRelation(entityId, alarmId, newStatus, false);
+ }
+
private <T> ListenableFuture<T> getAndUpdate(AlarmId alarmId, Function<Alarm, T> function) {
validateId(alarmId, "Alarm id should be specified!");
ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(alarmId.getId());
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
index 343d4cc..f5b6672 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.alarm;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
+import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -26,7 +27,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
@@ -86,15 +89,25 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
}
@Override
- public ListenableFuture<List<Alarm>> findAlarms(AlarmQuery query) {
- log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
+ public ListenableFuture<List<AlarmInfo>> findAlarms(AlarmQuery query) {
+ log.trace("Try to find alarms by entity [{}], searchStatus [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getSearchStatus(), query.getStatus(), query.getPageLink());
EntityId affectedEntity = query.getAffectedEntityId();
- String relationType = query.getStatus() == null ? BaseAlarmService.ALARM_RELATION : BaseAlarmService.ALARM_RELATION_PREFIX + query.getStatus().name();
+ String searchStatusName;
+ if (query.getSearchStatus() == null && query.getStatus() == null) {
+ searchStatusName = AlarmSearchStatus.ANY.name();
+ } else if (query.getSearchStatus() != null) {
+ searchStatusName = query.getSearchStatus().name();
+ } else {
+ searchStatusName = query.getStatus().name();
+ }
+ String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName;
ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
- return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<Alarm>>) input -> {
- List<ListenableFuture<Alarm>> alarmFutures = new ArrayList<>(input.size());
+ return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<AlarmInfo>>) input -> {
+ List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.size());
for (EntityRelation relation : input) {
- alarmFutures.add(findAlarmByIdAsync(relation.getTo().getId()));
+ alarmFutures.add(Futures.transform(
+ findAlarmByIdAsync(relation.getTo().getId()), (Function<Alarm, AlarmInfo>)
+ alarm1 -> new AlarmInfo(alarm1)));
}
return Futures.successfulAsList(alarmFutures);
});
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
index 0fdd6ca..2f6fe73 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java
@@ -179,9 +179,14 @@ public class BaseRelationDao extends CassandraAbstractAsyncDao implements Relati
eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()),
eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType),
eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())),
- Arrays.asList(QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY),
- QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY),
- QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)),
+ Arrays.asList(
+ pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY) :
+ QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY),
+ pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_PROPERTY) :
+ QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY),
+ pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TO_TYPE_PROPERTY) :
+ QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY)
+ ),
pageLink, ModelConstants.RELATION_TO_ID_PROPERTY);
return getFuture(executeAsyncRead(query), rs -> getEntityRelations(rs));
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
index 36ec567..296874e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
@@ -191,7 +191,7 @@ public class BaseRelationService implements RelationService {
@Override
public ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query) {
- log.trace("Executing findByQuery [{}][{}]", query);
+ log.trace("Executing findByQuery [{}]", query);
RelationsSearchParameters params = query.getParameters();
final List<EntityTypeFilter> filters = query.getFilters();
if (filters == null || filters.isEmpty()) {
@@ -224,6 +224,30 @@ public class BaseRelationService implements RelationService {
}
}
+ @Override
+ public ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query) {
+ log.trace("Executing findInfoByQuery [{}]", query);
+ ListenableFuture<List<EntityRelation>> relations = findByQuery(query);
+ EntitySearchDirection direction = query.getParameters().getDirection();
+ ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
+ (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
+ List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
+ relations1.stream().forEach(relation ->
+ futures.add(fetchRelationInfoAsync(relation,
+ relation2 -> direction == EntitySearchDirection.FROM ? relation2.getTo() : relation2.getFrom(),
+ (EntityRelationInfo relationInfo, String entityName) -> {
+ if (direction == EntitySearchDirection.FROM) {
+ relationInfo.setToName(entityName);
+ } else {
+ relationInfo.setFromName(entityName);
+ }
+ }))
+ );
+ return Futures.successfulAsList(futures);
+ });
+ return relationsInfo;
+ }
+
protected void validate(EntityRelation relation) {
if (relation == null) {
throw new DataValidationException("Relation type should be specified!");
diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
index a810454..bd2e785 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
@@ -52,6 +52,8 @@ public interface RelationService {
ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
+ ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(EntityRelationsQuery query);
+
// TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
index 3b72574..b22df63 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
@@ -22,10 +22,7 @@ import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmQuery;
-import org.thingsboard.server.common.data.alarm.AlarmSeverity;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.*;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -117,7 +114,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
Alarm created = alarmService.createOrUpdateAlarm(alarm);
// Check child relation
- TimePageData<Alarm> alarms = alarmService.findAlarms(AlarmQuery.builder()
+ TimePageData<AlarmInfo> alarms = alarmService.findAlarms(AlarmQuery.builder()
.affectedEntityId(childId)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
ui/src/app/alarm/alarm.scss 78(+78 -0)
diff --git a/ui/src/app/alarm/alarm.scss b/ui/src/app/alarm/alarm.scss
new file mode 100644
index 0000000..aea5225
--- /dev/null
+++ b/ui/src/app/alarm/alarm.scss
@@ -0,0 +1,78 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-alarm-container {
+ overflow-x: auto;
+}
+
+md-list.tb-alarm-table {
+ padding: 0px;
+ min-width: 700px;
+
+ md-list-item {
+ padding: 0px;
+ }
+
+ .tb-row {
+ height: 48px;
+ padding: 0px;
+ overflow: hidden;
+ }
+
+ .tb-row:hover {
+ background-color: #EEEEEE;
+ }
+
+ .tb-header:hover {
+ background: none;
+ }
+
+ .tb-header {
+ .tb-cell {
+ color: rgba(0,0,0,.54);
+ font-size: 12px;
+ font-weight: 700;
+ white-space: nowrap;
+ background: none;
+ }
+ }
+
+ .tb-cell {
+ padding: 0 24px;
+ margin: auto 0;
+ color: rgba(0,0,0,.87);
+ font-size: 13px;
+ vertical-align: middle;
+ text-align: left;
+ overflow: hidden;
+ .md-button {
+ padding: 0;
+ margin: 0;
+ }
+ }
+
+ .tb-cell.tb-number {
+ text-align: right;
+ }
+
+}
+
+#tb-alarm-content {
+ min-width: 400px;
+ min-height: 50px;
+ width: 100%;
+ height: 100%;
+}
ui/src/app/alarm/alarm-details-dialog.controller.js 134(+134 -0)
diff --git a/ui/src/app/alarm/alarm-details-dialog.controller.js b/ui/src/app/alarm/alarm-details-dialog.controller.js
new file mode 100644
index 0000000..0cc05ef
--- /dev/null
+++ b/ui/src/app/alarm/alarm-details-dialog.controller.js
@@ -0,0 +1,134 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import 'brace/ext/language_tools';
+import 'brace/mode/json';
+import 'brace/theme/github';
+import beautify from 'js-beautify';
+
+import './alarm-details-dialog.scss';
+
+const js_beautify = beautify.js;
+
+/*@ngInject*/
+export default function AlarmDetailsDialogController($mdDialog, $filter, $translate, types, alarmService, alarmId, showingCallback) {
+
+ var vm = this;
+
+ vm.alarmId = alarmId;
+ vm.types = types;
+ vm.alarm = null;
+
+ vm.alarmUpdated = false;
+
+ showingCallback.onShowing = function(scope, element) {
+ updateEditorSize(element);
+ }
+
+ vm.alarmDetailsOptions = {
+ useWrapMode: false,
+ mode: 'json',
+ showGutter: false,
+ showPrintMargin: false,
+ theme: 'github',
+ advanced: {
+ enableSnippets: false,
+ enableBasicAutocompletion: false,
+ enableLiveAutocompletion: false
+ },
+ onLoad: function (_ace) {
+ vm.editor = _ace;
+ }
+ };
+
+ vm.close = close;
+ vm.acknowledge = acknowledge;
+ vm.clear = clear;
+
+ loadAlarm();
+
+ function updateEditorSize(element) {
+ var newWidth = 600;
+ var newHeight = 200;
+ angular.element('#tb-alarm-details', element).height(newHeight.toString() + "px")
+ .width(newWidth.toString() + "px");
+ vm.editor.resize();
+ }
+
+ function loadAlarm() {
+ alarmService.getAlarmInfo(vm.alarmId).then(
+ function success(alarm) {
+ vm.alarm = alarm;
+ loadAlarmFields();
+ },
+ function fail() {
+ vm.alarm = null;
+ }
+ );
+ }
+
+ function loadAlarmFields() {
+ vm.createdTime = $filter('date')(vm.alarm.createdTime, 'yyyy-MM-dd HH:mm:ss');
+ vm.startTime = null;
+ if (vm.alarm.startTs) {
+ vm.startTime = $filter('date')(vm.alarm.startTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+ vm.endTime = null;
+ if (vm.alarm.endTs) {
+ vm.endTime = $filter('date')(vm.alarm.endTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+ vm.ackTime = null;
+ if (vm.alarm.ackTs) {
+ vm.ackTime = $filter('date')(vm.alarm.ackTs, 'yyyy-MM-dd HH:mm:ss')
+ }
+ vm.clearTime = null;
+ if (vm.alarm.clearTs) {
+ vm.clearTime = $filter('date')(vm.alarm.clearTs, 'yyyy-MM-dd HH:mm:ss');
+ }
+
+ vm.alarmSeverity = $translate.instant(types.alarmSeverity[vm.alarm.severity].name);
+
+ vm.alarmStatus = $translate.instant('alarm.display-status.' + vm.alarm.status);
+
+ vm.alarmDetails = null;
+ if (vm.alarm.details) {
+ vm.alarmDetails = angular.toJson(vm.alarm.details);
+ vm.alarmDetails = js_beautify(vm.alarmDetails, {indent_size: 4});
+ }
+ }
+
+ function acknowledge () {
+ alarmService.ackAlarm(vm.alarmId).then(
+ function success() {
+ vm.alarmUpdated = true;
+ loadAlarm();
+ }
+ );
+ }
+
+ function clear () {
+ alarmService.clearAlarm(vm.alarmId).then(
+ function success() {
+ vm.alarmUpdated = true;
+ loadAlarm();
+ }
+ );
+ }
+
+ function close () {
+ $mdDialog.hide(vm.alarmUpdated ? vm.alarm : null);
+ }
+
+}
ui/src/app/alarm/alarm-details-dialog.scss 27(+27 -0)
diff --git a/ui/src/app/alarm/alarm-details-dialog.scss b/ui/src/app/alarm/alarm-details-dialog.scss
new file mode 100644
index 0000000..9b923d0
--- /dev/null
+++ b/ui/src/app/alarm/alarm-details-dialog.scss
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-alarm-details-panel {
+ margin-left: 15px;
+ border: 1px solid #C0C0C0;
+ height: 100%;
+ #tb-alarm-details {
+ min-width: 600px;
+ min-height: 200px;
+ width: 100%;
+ height: 100%;
+ }
+}
ui/src/app/alarm/alarm-details-dialog.tpl.html 107(+107 -0)
diff --git a/ui/src/app/alarm/alarm-details-dialog.tpl.html b/ui/src/app/alarm/alarm-details-dialog.tpl.html
new file mode 100644
index 0000000..c958201
--- /dev/null
+++ b/ui/src/app/alarm/alarm-details-dialog.tpl.html
@@ -0,0 +1,107 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog aria-label="{{ 'alarm.alarm-details' | translate }}">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2 translate>alarm.alarm-details</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.close()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-dialog-content>
+ <div class="md-dialog-content" layout="column">
+ <div layout="row">
+ <md-input-container class="md-block">
+ <label translate>alarm.created-time</label>
+ <input ng-model="vm.createdTime" readonly>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alarm.originator</label>
+ <input ng-model="vm.alarm.originatorName" readonly>
+ </md-input-container>
+ </div>
+ <div layout="row" ng-if="vm.startTime || vm.endTime">
+ <md-input-container ng-if="vm.startTime" flex class="md-block">
+ <label translate>alarm.start-time</label>
+ <input ng-model="vm.startTime" readonly>
+ </md-input-container>
+ <md-input-container ng-if="vm.endTime" flex class="md-block">
+ <label translate>alarm.end-time</label>
+ <input ng-model="vm.endTime" readonly>
+ </md-input-container>
+ <span flex ng-if="!vm.startTime || !vm.endTime"></span>
+ </div>
+ <div layout="row" ng-if="vm.ackTime || vm.clearTime">
+ <md-input-container ng-if="vm.ackTime" flex class="md-block">
+ <label translate>alarm.ack-time</label>
+ <input ng-model="vm.ackTime" readonly>
+ </md-input-container>
+ <md-input-container ng-if="vm.clearTime" flex class="md-block">
+ <label translate>alarm.clear-time</label>
+ <input ng-model="vm.clearTime" readonly>
+ </md-input-container>
+ <span flex ng-if="!vm.ackTime || !vm.clearTime"></span>
+ </div>
+ <div layout="row">
+ <md-input-container flex class="md-block">
+ <label translate>alarm.type</label>
+ <input ng-model="vm.alarm.type" readonly>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alarm.severity</label>
+ <input class="tb-severity" ng-class="vm.types.alarmSeverity[vm.alarm.severity].class"
+ ng-model="vm.alarmSeverity" readonly>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alarm.status</label>
+ <input ng-model="vm.alarmStatus" readonly>
+ </md-input-container>
+ </div>
+ <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>alarm.details</div>
+ <div flex class="tb-alarm-details-panel" layout="column">
+ <div flex id="tb-alarm-details" readonly
+ ui-ace="vm.alarmDetailsOptions"
+ ng-model="vm.alarmDetails">
+ </div>
+ </div>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeUnack ||
+ vm.alarm.status==vm.types.alarmStatus.clearedUnack"
+ class="md-raised md-primary"
+ ng-disabled="loading"
+ ng-click="vm.acknowledge()"
+ style="margin-right:20px;">{{ 'alarm.acknowledge' |
+ translate }}
+ </md-button>
+ <md-button ng-if="vm.alarm.status==vm.types.alarmStatus.activeAck ||
+ vm.alarm.status==vm.types.alarmStatus.activeUnack"
+ class="md-raised md-primary"
+ ng-disabled="loading"
+ ng-click="vm.clear()">{{ 'alarm.clear' |
+ translate }}
+ </md-button>
+ <span flex></span>
+ <md-button ng-disabled="loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |
+ translate }}
+ </md-button>
+ </md-dialog-actions>
+</md-dialog>
ui/src/app/alarm/alarm-header.directive.js 39(+39 -0)
diff --git a/ui/src/app/alarm/alarm-header.directive.js b/ui/src/app/alarm/alarm-header.directive.js
new file mode 100644
index 0000000..b66a972
--- /dev/null
+++ b/ui/src/app/alarm/alarm-header.directive.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* eslint-disable import/no-unresolved, import/default */
+
+import alarmHeaderTemplate from './alarm-header.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function AlarmHeaderDirective($compile, $templateCache) {
+
+ var linker = function (scope, element) {
+
+ var template = $templateCache.get(alarmHeaderTemplate);
+ element.html(template);
+ $compile(element.contents())(scope);
+
+ }
+
+ return {
+ restrict: "A",
+ replace: false,
+ link: linker,
+ scope: false
+ };
+}
ui/src/app/alarm/alarm-row.directive.js 67(+67 -0)
diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js
new file mode 100644
index 0000000..9cb9bed
--- /dev/null
+++ b/ui/src/app/alarm/alarm-row.directive.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* eslint-disable import/no-unresolved, import/default */
+
+import alarmDetailsDialogTemplate from './alarm-details-dialog.tpl.html';
+
+import alarmRowTemplate from './alarm-row.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function AlarmRowDirective($compile, $templateCache, types, $mdDialog, $document) {
+
+ var linker = function (scope, element, attrs) {
+
+ var template = $templateCache.get(alarmRowTemplate);
+ element.html(template);
+
+ scope.alarm = attrs.alarm;
+ scope.types = types;
+
+ scope.showAlarmDetails = function($event) {
+ var onShowingCallback = {
+ onShowing: function(){}
+ }
+ $mdDialog.show({
+ controller: 'AlarmDetailsDialogController',
+ controllerAs: 'vm',
+ templateUrl: alarmDetailsDialogTemplate,
+ locals: {alarmId: scope.alarm.id.id, showingCallback: onShowingCallback},
+ parent: angular.element($document[0].body),
+ targetEvent: $event,
+ fullscreen: true,
+ skipHide: true,
+ onShowing: function(scope, element) {
+ onShowingCallback.onShowing(scope, element);
+ }
+ }).then(function (alarm) {
+ if (alarm) {
+ scope.alarm = alarm;
+ }
+ });
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "A",
+ replace: false,
+ link: linker,
+ scope: false
+ };
+}
ui/src/app/alarm/alarm-table.directive.js 211(+211 -0)
diff --git a/ui/src/app/alarm/alarm-table.directive.js b/ui/src/app/alarm/alarm-table.directive.js
new file mode 100644
index 0000000..c93ea03
--- /dev/null
+++ b/ui/src/app/alarm/alarm-table.directive.js
@@ -0,0 +1,211 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import './alarm.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import alarmTableTemplate from './alarm-table.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function AlarmTableDirective($compile, $templateCache, $rootScope, types, alarmService) {
+
+ var linker = function (scope, element) {
+
+ var template = $templateCache.get(alarmTableTemplate);
+
+ element.html(template);
+
+ scope.types = types;
+
+ scope.alarmSearchStatus = types.alarmSearchStatus.any;
+
+ var pageSize = 20;
+ var startTime = 0;
+ var endTime = 0;
+
+ scope.timewindow = {
+ history: {
+ timewindowMs: 24 * 60 * 60 * 1000 // 1 day
+ }
+ }
+
+ scope.topIndex = 0;
+
+ scope.theAlarms = {
+ getItemAtIndex: function (index) {
+ if (index > scope.alarms.data.length) {
+ scope.theAlarms.fetchMoreItems_(index);
+ return null;
+ }
+ var item = scope.alarms.data[index];
+ if (item) {
+ item.indexNumber = index + 1;
+ }
+ return item;
+ },
+
+ getLength: function () {
+ if (scope.alarms.hasNext) {
+ return scope.alarms.data.length + scope.alarms.nextPageLink.limit;
+ } else {
+ return scope.alarms.data.length;
+ }
+ },
+
+ fetchMoreItems_: function () {
+ if (scope.alarms.hasNext && !scope.alarms.pending) {
+ if (scope.entityType && scope.entityId && scope.alarmSearchStatus) {
+ var promise = alarmService.getAlarms(scope.entityType, scope.entityId,
+ scope.alarms.nextPageLink, scope.alarmSearchStatus, null, true, false);
+ if (promise) {
+ scope.alarms.pending = true;
+ promise.then(
+ function success(alarms) {
+ scope.alarms.data = scope.alarms.data.concat(alarms.data);
+ scope.alarms.nextPageLink = alarms.nextPageLink;
+ scope.alarms.hasNext = alarms.hasNext;
+ if (scope.alarms.hasNext) {
+ scope.alarms.nextPageLink.limit = pageSize;
+ }
+ scope.alarms.pending = false;
+ },
+ function fail() {
+ scope.alarms.hasNext = false;
+ scope.alarms.pending = false;
+ });
+ } else {
+ scope.alarms.hasNext = false;
+ }
+ } else {
+ scope.alarms.hasNext = false;
+ }
+ }
+ }
+ };
+
+ scope.$watch("entityId", function(newVal, prevVal) {
+ if (newVal && !angular.equals(newVal, prevVal)) {
+ resetFilter();
+ reload();
+ }
+ });
+
+
+
+ function destroyWatchers() {
+ if (scope.alarmSearchStatusWatchHandle) {
+ scope.alarmSearchStatusWatchHandle();
+ scope.alarmSearchStatusWatchHandle = null;
+ }
+ if (scope.timewindowWatchHandle) {
+ scope.timewindowWatchHandle();
+ scope.timewindowWatchHandle = null;
+ }
+ }
+
+ function initWatchers() {
+ scope.alarmSearchStatusWatchHandle = scope.$watch("alarmSearchStatus", function(newVal, prevVal) {
+ if (newVal && !angular.equals(newVal, prevVal)) {
+ reload();
+ }
+ });
+ scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) {
+ if (newVal && !angular.equals(newVal, prevVal)) {
+ reload();
+ }
+ }, true);
+ }
+
+ function resetFilter() {
+ destroyWatchers();
+ scope.timewindow = {
+ history: {
+ timewindowMs: 24 * 60 * 60 * 1000 // 1 day
+ }
+ };
+ scope.alarmSearchStatus = types.alarmSearchStatus.any;
+ initWatchers();
+ }
+
+ function updateTimeWindowRange () {
+ if (scope.timewindow.history.timewindowMs) {
+ var currentTime = (new Date).getTime();
+ startTime = currentTime - scope.timewindow.history.timewindowMs;
+ endTime = currentTime;
+ } else {
+ startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;
+ endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;
+ }
+ }
+
+ function reload () {
+ scope.topIndex = 0;
+ scope.selected = [];
+ updateTimeWindowRange();
+ scope.alarms = {
+ data: [],
+ nextPageLink: {
+ limit: pageSize,
+ startTime: startTime,
+ endTime: endTime
+ },
+ hasNext: true,
+ pending: false
+ };
+ scope.theAlarms.getItemAtIndex(pageSize);
+ }
+
+ scope.noData = function() {
+ return scope.alarms.data.length == 0 && !scope.alarms.hasNext;
+ }
+
+ scope.hasData = function() {
+ return scope.alarms.data.length > 0;
+ }
+
+ scope.loading = function() {
+ return $rootScope.loading;
+ }
+
+ scope.hasScroll = function() {
+ var repeatContainer = scope.repeatContainer[0];
+ if (repeatContainer) {
+ var scrollElement = repeatContainer.children[0];
+ if (scrollElement) {
+ return scrollElement.scrollHeight > scrollElement.clientHeight;
+ }
+ }
+ return false;
+ }
+
+ reload();
+
+ initWatchers();
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ link: linker,
+ scope: {
+ entityType: '=',
+ entityId: '='
+ }
+ };
+}
ui/src/app/alarm/alarm-table.tpl.html 49(+49 -0)
diff --git a/ui/src/app/alarm/alarm-table.tpl.html b/ui/src/app/alarm/alarm-table.tpl.html
new file mode 100644
index 0000000..c32e39a
--- /dev/null
+++ b/ui/src/app/alarm/alarm-table.tpl.html
@@ -0,0 +1,49 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-content flex class="md-padding tb-absolute-fill" layout="column">
+ <section layout="row">
+ <md-input-container class="md-block" style="width: 200px;">
+ <label translate>alarm.alarm-status</label>
+ <md-select ng-model="alarmSearchStatus" ng-disabled="loading()">
+ <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
+ {{ ('alarm.search-status.' + searchStatus) | translate }}
+ </md-option>
+ </md-select>
+ </md-input-container>
+ <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
+ </section>
+ <div flex layout="column" class="tb-alarm-container md-whiteframe-z1">
+ <md-list flex layout="column" class="tb-alarm-table">
+ <md-list class="tb-row tb-header" layout="row" tb-alarm-header>
+ </md-list>
+ <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"
+ ng-show="loading()"></md-progress-linear>
+ <md-divider></md-divider>
+ <span translate layout-align="center center"
+ style="margin-top: 25px;"
+ class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span>
+ <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
+ <md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
+ <md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}">
+ </md-list>
+ <md-divider flex></md-divider>
+ </md-list-item>
+ </md-virtual-repeat-container>
+ </md-list>
+ </div>
+</md-content>
ui/src/app/alarm/index.js 27(+27 -0)
diff --git a/ui/src/app/alarm/index.js b/ui/src/app/alarm/index.js
new file mode 100644
index 0000000..0ea4610
--- /dev/null
+++ b/ui/src/app/alarm/index.js
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import AlarmDetailsDialogController from './alarm-details-dialog.controller';
+import AlarmHeaderDirective from './alarm-header.directive';
+import AlarmRowDirective from './alarm-row.directive';
+import AlarmTableDirective from './alarm-table.directive';
+
+export default angular.module('thingsboard.alarm', [])
+ .controller('AlarmDetailsDialogController', AlarmDetailsDialogController)
+ .directive('tbAlarmHeader', AlarmHeaderDirective)
+ .directive('tbAlarmRow', AlarmRowDirective)
+ .directive('tbAlarmTable', AlarmTableDirective)
+ .name;
ui/src/app/api/alarm.service.js 29(+27 -2)
diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js
index d9ab4ff..34e6b59 100644
--- a/ui/src/app/api/alarm.service.js
+++ b/ui/src/app/api/alarm.service.js
@@ -21,6 +21,7 @@ export default angular.module('thingsboard.api.alarm', [])
function AlarmService($http, $q, $interval, $filter) {
var service = {
getAlarm: getAlarm,
+ getAlarmInfo: getAlarmInfo,
saveAlarm: saveAlarm,
ackAlarm: ackAlarm,
clearAlarm: clearAlarm,
@@ -46,6 +47,21 @@ function AlarmService($http, $q, $interval, $filter) {
return deferred.promise;
}
+ function getAlarmInfo(alarmId, ignoreErrors, config) {
+ var deferred = $q.defer();
+ var url = '/api/alarm/info/' + alarmId;
+ if (!config) {
+ config = {};
+ }
+ config = Object.assign(config, { ignoreErrors: ignoreErrors });
+ $http.get(url, config).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
function saveAlarm(alarm, ignoreErrors, config) {
var deferred = $q.defer();
var url = '/api/alarm';
@@ -91,7 +107,7 @@ function AlarmService($http, $q, $interval, $filter) {
return deferred.promise;
}
- function getAlarms(entityType, entityId, pageLink, alarmStatus, ascOrder, config) {
+ function getAlarms(entityType, entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, ascOrder, config) {
var deferred = $q.defer();
var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit;
@@ -104,9 +120,15 @@ function AlarmService($http, $q, $interval, $filter) {
if (angular.isDefined(pageLink.idOffset)) {
url += '&offset=' + pageLink.idOffset;
}
+ if (alarmSearchStatus) {
+ url += '&searchStatus=' + alarmSearchStatus;
+ }
if (alarmStatus) {
url += '&status=' + alarmStatus;
}
+ if (fetchOriginator) {
+ url += '&fetchOriginator=' + ((fetchOriginator===true) ? 'true' : 'false');
+ }
if (angular.isDefined(ascOrder) && ascOrder != null) {
url += '&ascOrder=' + (ascOrder ? 'true' : 'false');
}
@@ -121,7 +143,8 @@ function AlarmService($http, $q, $interval, $filter) {
function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) {
getAlarms(alarmsQuery.entityType, alarmsQuery.entityId,
- pageLink, alarmsQuery.alarmStatus, false, {ignoreLoading: true}).then(
+ pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus,
+ alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then(
function success(alarms) {
if (!alarmsList) {
alarmsList = [];
@@ -171,7 +194,9 @@ function AlarmService($http, $q, $interval, $filter) {
var alarmsQuery = {
entityType: entityType,
entityId: entityId,
+ alarmSearchStatus: null,
alarmStatus: alarmStatus,
+ fetchOriginator: false,
interval: interval,
limit: limit,
onAlarms: onAlarms
ui/src/app/api/alias-controller.js 279(+279 -0)
diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js
new file mode 100644
index 0000000..60e8a31
--- /dev/null
+++ b/ui/src/app/api/alias-controller.js
@@ -0,0 +1,279 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const varsRegex = /\$\{([^\}]*)\}/g;
+
+export default class AliasController {
+
+ constructor($scope, $q, $filter, utils, types, entityService, stateController, entityAliases) {
+ this.$scope = $scope;
+ this.$q = $q;
+ this.$filter = $filter;
+ this.utils = utils;
+ this.types = types;
+ this.entityService = entityService;
+ this.stateController = stateController;
+ this.entityAliases = angular.copy(entityAliases);
+ this.resolvedAliases = {};
+ this.resolvedAliasesPromise = {};
+ this.resolvedAliasesToStateEntities = {};
+ }
+
+ updateEntityAliases(newEntityAliases) {
+ var changedAliasIds = [];
+ for (var aliasId in newEntityAliases) {
+ var newEntityAlias = newEntityAliases[aliasId];
+ var prevEntityAlias = this.entityAliases[aliasId];
+ if (!angular.equals(newEntityAlias, prevEntityAlias)) {
+ changedAliasIds.push(aliasId);
+ this.setAliasUnresolved(aliasId);
+ }
+ }
+ for (aliasId in this.entityAliases) {
+ if (!newEntityAliases[aliasId]) {
+ changedAliasIds.push(aliasId);
+ this.setAliasUnresolved(aliasId);
+ }
+ }
+ this.entityAliases = angular.copy(newEntityAliases);
+ if (changedAliasIds.length) {
+ this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
+ }
+ }
+
+ dashboardStateChanged() {
+ var newEntityId = this.stateController.getStateParams().entityId;
+ var changedAliasIds = [];
+ for (var aliasId in this.resolvedAliasesToStateEntities) {
+ var prevEntityId = this.resolvedAliasesToStateEntities[aliasId];
+ if (!angular.equals(newEntityId, prevEntityId)) {
+ changedAliasIds.push(aliasId);
+ this.setAliasUnresolved(aliasId);
+ }
+ }
+ if (changedAliasIds.length) {
+ this.$scope.$broadcast('entityAliasesChanged', changedAliasIds);
+ }
+ }
+
+ setAliasUnresolved(aliasId) {
+ delete this.resolvedAliases[aliasId];
+ delete this.resolvedAliasesPromise[aliasId];
+ delete this.resolvedAliasesToStateEntities[aliasId];
+ }
+
+ getEntityAliases() {
+ return this.entityAliases;
+ }
+
+ getAliasInfo(aliasId) {
+ var deferred = this.$q.defer();
+ var aliasInfo = this.resolvedAliases[aliasId];
+ if (aliasInfo) {
+ deferred.resolve(aliasInfo);
+ return deferred.promise;
+ } else if (this.resolvedAliasesPromise[aliasId]) {
+ return this.resolvedAliasesPromise[aliasId];
+ } else {
+ this.resolvedAliasesPromise[aliasId] = deferred.promise;
+ var aliasCtrl = this;
+ var entityAlias = this.entityAliases[aliasId];
+ if (entityAlias) {
+ this.entityService.resolveAlias(entityAlias, this.stateController.getStateParams()).then(
+ function success(aliasInfo) {
+ aliasCtrl.resolvedAliases[aliasId] = aliasInfo;
+ if (aliasInfo.stateEntity) {
+ aliasCtrl.resolvedAliasesToStateEntities[aliasId] =
+ aliasCtrl.stateController.getStateParams().entityId;
+ }
+ aliasCtrl.$scope.$broadcast('entityAliasResolved', aliasId);
+ deferred.resolve(aliasInfo);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ return this.resolvedAliasesPromise[aliasId];
+ }
+ }
+
+ resolveDatasource(datasource) {
+ var deferred = this.$q.defer();
+ if (datasource.type === this.types.datasourceType.entity) {
+ if (datasource.entityAliasId) {
+ this.getAliasInfo(datasource.entityAliasId).then(
+ function success(aliasInfo) {
+ datasource.aliasName = aliasInfo.alias;
+ if (aliasInfo.resolveMultiple) {
+ var newDatasource;
+ var resolvedEntities = aliasInfo.resolvedEntities;
+ if (resolvedEntities && resolvedEntities.length) {
+ var datasources = [];
+ for (var i=0;i<resolvedEntities.length;i++) {
+ var resolvedEntity = resolvedEntities[i];
+ newDatasource = angular.copy(datasource);
+ newDatasource.entityId = resolvedEntity.id;
+ newDatasource.entityType = resolvedEntity.entityType;
+ newDatasource.entityName = resolvedEntity.name;
+ newDatasource.name = resolvedEntity.name;
+ newDatasource.generated = i > 0 ? true : false;
+ datasources.push(newDatasource);
+ }
+ deferred.resolve(datasources);
+ } else {
+ if (aliasInfo.stateEntity) {
+ newDatasource = angular.copy(datasource);
+ newDatasource.unresolvedStateEntity = true;
+ deferred.resolve([newDatasource]);
+ } else {
+ deferred.reject();
+ }
+ }
+ } else {
+ var entity = aliasInfo.currentEntity;
+ if (entity) {
+ datasource.entityId = entity.id;
+ datasource.entityType = entity.entityType;
+ datasource.entityName = entity.name;
+ datasource.name = entity.name;
+ deferred.resolve([datasource]);
+ } else {
+ if (aliasInfo.stateEntity) {
+ datasource.unresolvedStateEntity = true;
+ deferred.resolve([datasource]);
+ } else {
+ deferred.reject();
+ }
+ }
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else { // entityId
+ datasource.aliasName = datasource.entityName;
+ datasource.name = datasource.entityName;
+ deferred.resolve([datasource]);
+ }
+ } else { // function
+ deferred.resolve([datasource]);
+ }
+ return deferred.promise;
+ }
+
+ resolveDatasources(datasources) {
+
+ function updateDataKeyLabel(dataKey, datasource) {
+ if (!dataKey.pattern) {
+ dataKey.pattern = angular.copy(dataKey.label);
+ }
+ var pattern = dataKey.pattern;
+ var label = dataKey.pattern;
+ var match = varsRegex.exec(pattern);
+ while (match !== null) {
+ var variable = match[0];
+ var variableName = match[1];
+ if (variableName === 'dsName') {
+ label = label.split(variable).join(datasource.name);
+ } else if (variableName === 'entityName') {
+ label = label.split(variable).join(datasource.entityName);
+ } else if (variableName === 'deviceName') {
+ label = label.split(variable).join(datasource.entityName);
+ } else if (variableName === 'aliasName') {
+ label = label.split(variable).join(datasource.aliasName);
+ }
+ match = varsRegex.exec(pattern);
+ }
+ dataKey.label = label;
+ }
+
+ function updateDatasourceKeyLabels(datasource) {
+ for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
+ updateDataKeyLabel(datasource.dataKeys[dk], datasource);
+ }
+ }
+
+ var deferred = this.$q.defer();
+ var newDatasources = angular.copy(datasources);
+ var datasorceResolveTasks = [];
+ var aliasCtrl = this;
+ newDatasources.forEach(function (datasource) {
+ var resolveDatasourceTask = aliasCtrl.resolveDatasource(datasource);
+ datasorceResolveTasks.push(resolveDatasourceTask);
+ });
+ this.$q.all(datasorceResolveTasks).then(
+ function success(datasourcesArrays) {
+ var datasources = [].concat.apply([], datasourcesArrays);
+ datasources = aliasCtrl.$filter('orderBy')(datasources, '+generated');
+ var index = 0;
+ var functionIndex = 0;
+ datasources.forEach(function(datasource) {
+ if (datasource.type === aliasCtrl.types.datasourceType.function) {
+ var name;
+ if (datasource.name && datasource.name.length) {
+ name = datasource.name;
+ } else {
+ functionIndex++;
+ name = aliasCtrl.types.datasourceType.function;
+ if (functionIndex > 1) {
+ name += ' ' + functionIndex;
+ }
+ }
+ datasource.name = name;
+ datasource.aliasName = name;
+ datasource.entityName = name;
+ } else if (datasource.unresolvedStateEntity) {
+ datasource.name = "Unresolved";
+ datasource.entityName = "Unresolved";
+ }
+ datasource.dataKeys.forEach(function(dataKey) {
+ if (datasource.generated) {
+ dataKey._hash = Math.random();
+ dataKey.color = aliasCtrl.utils.getMaterialColor(index);
+ }
+ index++;
+ });
+ updateDatasourceKeyLabels(datasource);
+ });
+ deferred.resolve(datasources);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ getInstantAliasInfo(aliasId) {
+ return this.resolvedAliases[aliasId];
+ }
+
+ updateCurrentAliasEntity(aliasId, currentEntity) {
+ var aliasInfo = this.resolvedAliases[aliasId];
+ if (aliasInfo) {
+ var prevCurrentEntity = aliasInfo.currentEntity;
+ if (!angular.equals(currentEntity, prevCurrentEntity)) {
+ aliasInfo.currentEntity = currentEntity;
+ this.$scope.$broadcast('entityAliasesChanged', [aliasId]);
+ }
+ }
+ }
+
+}
\ No newline at end of file
ui/src/app/api/entity.service.js 563(+463 -100)
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 891f9d8..20157ce 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -27,10 +27,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
getEntity: getEntity,
getEntities: getEntities,
getEntitiesByNameFilter: getEntitiesByNameFilter,
- processEntityAliases: processEntityAliases,
- getEntityKeys: getEntityKeys,
+ resolveAlias: resolveAlias,
+ resolveAliasFilter: resolveAliasFilter,
checkEntityAlias: checkEntityAlias,
- createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
+ filterAliasByEntityTypes: filterAliasByEntityTypes,
+ getAliasFilterTypesByEntityTypes: getAliasFilterTypesByEntityTypes,
+ prepareAllowedEntityTypesList: prepareAllowedEntityTypesList,
+ getEntityKeys: getEntityKeys,
+ createDatasourcesFromSubscriptionsInfo: createDatasourcesFromSubscriptionsInfo,
getRelatedEntities: getRelatedEntities,
saveRelatedEntity: saveRelatedEntity,
getRelatedEntity: getRelatedEntity,
@@ -173,6 +177,54 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return deferred.promise;
}
+ function getSingleTenantByPageLinkPromise(pageLink) {
+ var user = userService.getCurrentUser();
+ var tenantId = user.tenantId;
+ var deferred = $q.defer();
+ tenantService.getTenant(tenantId).then(
+ function success(tenant) {
+ var tenantName = tenant.name;
+ var result = {
+ data: [],
+ nextPageLink: pageLink,
+ hasNext: false
+ };
+ if (tenantName.toLowerCase().startsWith(pageLink.textSearch)) {
+ result.data.push(tenant);
+ }
+ deferred.resolve(result);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function getSingleCustomerByPageLinkPromise(pageLink) {
+ var user = userService.getCurrentUser();
+ var customerId = user.customerId;
+ var deferred = $q.defer();
+ customerService.getCustomer(customerId).then(
+ function success(customer) {
+ var customerName = customer.name;
+ var result = {
+ data: [],
+ nextPageLink: pageLink,
+ hasNext: false
+ };
+ if (customerName.toLowerCase().startsWith(pageLink.textSearch)) {
+ result.data.push(customer);
+ }
+ deferred.resolve(result);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
function getEntitiesByPageLinkPromise(entityType, pageLink, config, subType) {
var promise;
var user = userService.getCurrentUser();
@@ -193,10 +245,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
break;
case types.entityType.tenant:
- promise = tenantService.getTenants(pageLink);
+ if (user.authority === 'TENANT_ADMIN') {
+ promise = getSingleTenantByPageLinkPromise(pageLink);
+ } else {
+ promise = tenantService.getTenants(pageLink);
+ }
break;
case types.entityType.customer:
- promise = customerService.getCustomers(pageLink);
+ if (user.authority === 'CUSTOMER_USER') {
+ promise = getSingleCustomerByPageLinkPromise(pageLink);
+ } else {
+ promise = customerService.getCustomers(pageLink);
+ }
break;
case types.entityType.rule:
promise = ruleService.getAllRules(pageLink);
@@ -221,17 +281,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
return promise;
}
- function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
- var deferred = $q.defer();
- var pageLink = {limit: limit, textSearch: entityNameFilter};
+ function getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred) {
var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
if (promise) {
promise.then(
function success(result) {
- if (result.data && result.data.length > 0) {
- deferred.resolve(result.data);
+ data = data.concat(result.data);
+ if (result.hasNext) {
+ pageLink = result.nextPageLink;
+ getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
} else {
- deferred.resolve(null);
+ if (data && data.length > 0) {
+ deferred.resolve(data);
+ } else {
+ deferred.resolve(null);
+ }
}
},
function fail() {
@@ -241,92 +305,418 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
} else {
deferred.resolve(null);
}
+ }
+
+ function getEntitiesByNameFilter(entityType, entityNameFilter, limit, config, subType) {
+ var deferred = $q.defer();
+ var pageLink = {limit: limit, textSearch: entityNameFilter};
+ if (limit == -1) { // all
+ var data = [];
+ pageLink.limit = 100;
+ getEntitiesByPageLink(entityType, pageLink, config, subType, data, deferred);
+ } else {
+ var promise = getEntitiesByPageLinkPromise(entityType, pageLink, config, subType);
+ if (promise) {
+ promise.then(
+ function success(result) {
+ if (result.data && result.data.length > 0) {
+ deferred.resolve(result.data);
+ } else {
+ deferred.resolve(null);
+ }
+ },
+ function fail() {
+ deferred.resolve(null);
+ }
+ );
+ } else {
+ deferred.resolve(null);
+ }
+ }
return deferred.promise;
}
- function entityToEntityInfo(entityType, entity) {
- return { name: entity.name, entityType: entityType, id: entity.id.id };
+ function entityToEntityInfo(entity) {
+ return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
}
- function entitiesToEntitiesInfo(entityType, entities) {
+ function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
+ var entityId = direction == types.entitySearchDirection.from ? entityRelationInfo.to : entityRelationInfo.from;
+ var name = direction == types.entitySearchDirection.from ? entityRelationInfo.toName : entityRelationInfo.fromName;
+ return {
+ name: name,
+ entityType: entityId.entityType,
+ id: entityId.id
+ };
+ }
+
+ function entitiesToEntitiesInfo(entities) {
var entitiesInfo = [];
for (var d = 0; d < entities.length; d++) {
- entitiesInfo.push(entityToEntityInfo(entityType, entities[d]));
+ entitiesInfo.push(entityToEntityInfo(entities[d]));
}
return entitiesInfo;
}
- function processEntityAlias(index, aliasIds, entityAliases, resolution, deferred) {
- if (index < aliasIds.length) {
- var aliasId = aliasIds[index];
- var entityAlias = entityAliases[aliasId];
- var alias = entityAlias.alias;
- var entityFilter = entityAlias.entityFilter;
- if (entityFilter.useFilter) {
- var entityNameFilter = entityFilter.entityNameFilter;
- getEntitiesByNameFilter(entityAlias.entityType, entityNameFilter, 100).then(
- function(entities) {
- if (entities && entities != null) {
- var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
- resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
- resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
- index++;
- processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
+ function entityRelationInfosToEntitiesInfo(entityRelations, direction) {
+ var entitiesInfo = [];
+ for (var d = 0; d < entityRelations.length; d++) {
+ entitiesInfo.push(entityRelationInfoToEntityInfo(entityRelations[d], direction));
+ }
+ return entitiesInfo;
+ }
+
+
+ function resolveAlias(entityAlias, stateParams) {
+ var deferred = $q.defer();
+ var filter = entityAlias.filter;
+ resolveAliasFilter(filter, stateParams, -1).then(
+ function (result) {
+ var aliasInfo = {
+ alias: entityAlias.alias,
+ stateEntity: result.stateEntity,
+ resolveMultiple: filter.resolveMultiple
+ };
+ aliasInfo.resolvedEntities = result.entities;
+ aliasInfo.currentEntity = null;
+ if (aliasInfo.resolvedEntities.length) {
+ aliasInfo.currentEntity = aliasInfo.resolvedEntities[0];
+ }
+ deferred.resolve(aliasInfo);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function resolveAliasFilter(filter, stateParams, maxItems) {
+ var deferred = $q.defer();
+ var result = {
+ entities: [],
+ stateEntity: false
+ };
+ switch (filter.type) {
+ case types.aliasFilterType.entityList.value:
+ getEntities(filter.entityType, filter.entityList).then(
+ function success(entities) {
+ if (entities && entities.length) {
+ result.entities = entitiesToEntitiesInfo(entities);
+ deferred.resolve(result);
} else {
- if (!resolution.error) {
- resolution.error = 'dashboard.invalid-aliases-config';
- }
- index++;
- processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
+ deferred.reject();
}
- });
- } else {
- var entityList = entityFilter.entityList;
- getEntities(entityAlias.entityType, entityList).then(
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ break;
+ case types.aliasFilterType.entityName.value:
+ getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then(
function success(entities) {
- if (entities && entities.length > 0) {
- var resolvedAlias = {alias: alias, entityType: entityAlias.entityType, entityId: entities[0].id.id};
- resolution.aliasesInfo.entityAliases[aliasId] = resolvedAlias;
- resolution.aliasesInfo.entityAliasesInfo[aliasId] = entitiesToEntitiesInfo(entityAlias.entityType, entities);
- index++;
- processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
+ if (entities && entities.length) {
+ result.entities = entitiesToEntitiesInfo(entities);
+ deferred.resolve(result);
} else {
- if (!resolution.error) {
- resolution.error = 'dashboard.invalid-aliases-config';
- }
- index++;
- processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
+ deferred.reject();
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ break;
+ case types.aliasFilterType.stateEntity.value:
+ result.stateEntity = true;
+ if (stateParams && stateParams.entityId) {
+ getEntity(stateParams.entityId.entityType, stateParams.entityId.id).then(
+ function success(entity) {
+ result.entities = entitiesToEntitiesInfo([entity]);
+ deferred.resolve(result);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.resolve(result);
+ }
+ break;
+ case types.aliasFilterType.assetType.value:
+ getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then(
+ function success(entities) {
+ if (entities && entities.length) {
+ result.entities = entitiesToEntitiesInfo(entities);
+ deferred.resolve(result);
+ } else {
+ deferred.reject();
}
},
function fail() {
- if (!resolution.error) {
- resolution.error = 'dashboard.invalid-aliases-config';
+ deferred.reject();
+ }
+ );
+ break;
+ case types.aliasFilterType.deviceType.value:
+ getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then(
+ function success(entities) {
+ if (entities && entities.length) {
+ result.entities = entitiesToEntitiesInfo(entities);
+ deferred.resolve(result);
+ } else {
+ deferred.reject();
}
- index++;
- processEntityAlias(index, aliasIds, entityAliases, resolution, deferred);
+ },
+ function fail() {
+ deferred.reject();
}
);
+ break;
+ case types.aliasFilterType.relationsQuery.value:
+ result.stateEntity = filter.rootStateEntity;
+ var rootEntityType;
+ var rootEntityId;
+ if (result.stateEntity && stateParams && stateParams.entityId) {
+ rootEntityType = stateParams.entityId.entityType;
+ rootEntityId = stateParams.entityId.id;
+ } else if (!result.stateEntity) {
+ rootEntityType = filter.rootEntity.entityType;
+ rootEntityId = filter.rootEntity.id;
+ }
+ if (rootEntityType && rootEntityId) {
+ var searchQuery = {
+ parameters: {
+ rootId: rootEntityId,
+ rootType: rootEntityType,
+ direction: filter.direction
+ },
+ filters: filter.filters
+ };
+ searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
+ entityRelationService.findInfoByQuery(searchQuery).then(
+ function success(allRelations) {
+ if (allRelations && allRelations.length) {
+ if (angular.isDefined(maxItems) && maxItems > 0) {
+ var limit = Math.min(allRelations.length, maxItems);
+ allRelations.length = limit;
+ }
+ result.entities = entityRelationInfosToEntitiesInfo(allRelations, filter.direction);
+ deferred.resolve(result);
+ } else {
+ deferred.reject();
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.resolve(result);
+ }
+ break;
+ case types.aliasFilterType.assetSearchQuery.value:
+ case types.aliasFilterType.deviceSearchQuery.value:
+ result.stateEntity = filter.rootStateEntity;
+ if (result.stateEntity && stateParams && stateParams.entityId) {
+ rootEntityType = stateParams.entityId.entityType;
+ rootEntityId = stateParams.entityId.id;
+ } else if (!result.stateEntity) {
+ rootEntityType = filter.rootEntity.entityType;
+ rootEntityId = filter.rootEntity.id;
+ }
+ if (rootEntityType && rootEntityId) {
+ searchQuery = {
+ parameters: {
+ rootId: rootEntityId,
+ rootType: rootEntityType,
+ direction: filter.direction
+ },
+ relationType: filter.relationType
+ };
+ searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
+ var findByQueryPromise;
+ if (filter.type == types.aliasFilterType.assetSearchQuery.value) {
+ searchQuery.assetTypes = filter.assetTypes;
+ findByQueryPromise = assetService.findByQuery(searchQuery, false);
+ } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) {
+ searchQuery.deviceTypes = filter.deviceTypes;
+ findByQueryPromise = deviceService.findByQuery(searchQuery, false);
+ }
+ findByQueryPromise.then(
+ function success(entities) {
+ if (entities && entities.length) {
+ if (angular.isDefined(maxItems) && maxItems > 0) {
+ var limit = Math.min(entities.length, maxItems);
+ entities.length = limit;
+ }
+ result.entities = entitiesToEntitiesInfo(entities);
+ deferred.resolve(result);
+ } else {
+ deferred.reject();
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.resolve(result);
+ }
+ break;
+ }
+ return deferred.promise;
+ }
+
+ function filterAliasByEntityTypes(entityAlias, entityTypes) {
+ var filter = entityAlias.filter;
+ if (filterAliasFilterTypeByEntityTypes(filter.type, entityTypes)) {
+ switch (filter.type) {
+ case types.aliasFilterType.entityList.value:
+ return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
+ case types.aliasFilterType.entityName.value:
+ return entityTypes.indexOf(filter.entityType) > -1 ? true : false;
+ case types.aliasFilterType.stateEntity.value:
+ return true;
+ case types.aliasFilterType.assetType.value:
+ return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
+ case types.aliasFilterType.deviceType.value:
+ return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
+ case types.aliasFilterType.relationsQuery.value:
+ if (filter.filters && filter.filters.length) {
+ var match = false;
+ for (var f=0;f<filter.filters.length;f++) {
+ var relationFilter = filter.filters[f];
+ if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
+ for (var et=0;et<relationFilter.entityTypes.length;et++) {
+ if (entityTypes.indexOf(relationFilter.entityTypes[et]) > -1) {
+ match = true;
+ break;
+ }
+ }
+ } else {
+ match = true;
+ break;
+ }
+ }
+ return match;
+ } else {
+ return true;
+ }
+ case types.aliasFilterType.assetSearchQuery.value:
+ return entityTypes.indexOf(types.entityType.asset) > -1 ? true : false;
+ case types.aliasFilterType.deviceSearchQuery.value:
+ return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
}
- } else {
- deferred.resolve(resolution);
}
+ return false;
}
- function processEntityAliases(entityAliases) {
- var deferred = $q.defer();
- var resolution = {
- aliasesInfo: {
- entityAliases: {},
- entityAliasesInfo: {}
+ function filterAliasFilterTypeByEntityType(aliasFilterType, entityType) {
+ switch (aliasFilterType) {
+ case types.aliasFilterType.entityList.value:
+ return true;
+ case types.aliasFilterType.entityName.value:
+ return true;
+ case types.aliasFilterType.stateEntity.value:
+ return true;
+ case types.aliasFilterType.assetType.value:
+ return entityType === types.entityType.asset;
+ case types.aliasFilterType.deviceType.value:
+ return entityType === types.entityType.device;
+ case types.aliasFilterType.relationsQuery.value:
+ return true;
+ case types.aliasFilterType.assetSearchQuery.value:
+ return entityType === types.entityType.asset;
+ case types.aliasFilterType.deviceSearchQuery.value:
+ return entityType === types.entityType.device;
+ }
+ return false;
+ }
+
+ function filterAliasFilterTypeByEntityTypes(aliasFilterType, entityTypes) {
+ if (!entityTypes || !entityTypes.length) {
+ return true;
+ }
+ var valid = false;
+ entityTypes.forEach(function(entityType) {
+ valid = valid || filterAliasFilterTypeByEntityType(aliasFilterType, entityType);
+ });
+ return valid;
+ }
+
+ function getAliasFilterTypesByEntityTypes(entityTypes) {
+ var allAliasFilterTypes = types.aliasFilterType;
+ if (!entityTypes || !entityTypes.length) {
+ return allAliasFilterTypes;
+ }
+ var result = {};
+ for (var type in allAliasFilterTypes) {
+ var aliasFilterType = allAliasFilterTypes[type];
+ if (filterAliasFilterTypeByEntityTypes(aliasFilterType.value, entityTypes)) {
+ result[type] = aliasFilterType;
}
- };
- var aliasIds = [];
- if (entityAliases) {
- for (var aliasId in entityAliases) {
- aliasIds.push(aliasId);
+ }
+ return result;
+ }
+
+ function prepareAllowedEntityTypesList(allowedEntityTypes) {
+ var authority = userService.getAuthority();
+ var entityTypes = {};
+ switch(authority) {
+ case 'SYS_ADMIN':
+ entityTypes.tenant = types.entityType.tenant;
+ entityTypes.rule = types.entityType.rule;
+ entityTypes.plugin = types.entityType.plugin;
+ break;
+ case 'TENANT_ADMIN':
+ entityTypes.device = types.entityType.device;
+ entityTypes.asset = types.entityType.asset;
+ entityTypes.tenant = types.entityType.tenant;
+ entityTypes.customer = types.entityType.customer;
+ entityTypes.rule = types.entityType.rule;
+ entityTypes.plugin = types.entityType.plugin;
+ entityTypes.dashboard = types.entityType.dashboard;
+ break;
+ case 'CUSTOMER_USER':
+ entityTypes.device = types.entityType.device;
+ entityTypes.asset = types.entityType.asset;
+ entityTypes.customer = types.entityType.customer;
+ entityTypes.dashboard = types.entityType.dashboard;
+ break;
+ }
+
+ if (allowedEntityTypes) {
+ for (var entityType in entityTypes) {
+ if (allowedEntityTypes.indexOf(entityTypes[entityType]) === -1) {
+ delete entityTypes[entityType];
+ }
}
}
- processEntityAlias(0, aliasIds, entityAliases, resolution, deferred);
+ return entityTypes;
+ }
+
+
+ function checkEntityAlias(entityAlias) {
+ var deferred = $q.defer();
+ resolveAliasFilter(entityAlias.filter, null, 1).then(
+ function success(result) {
+ if (result.stateEntity) {
+ deferred.resolve(true);
+ } else {
+ var entities = result.entities;
+ if (entities && entities.length) {
+ deferred.resolve(true);
+ } else {
+ deferred.resolve(false);
+ }
+ }
+ },
+ function fail() {
+ deferred.resolve(false);
+ }
+ );
return deferred.promise;
}
@@ -354,40 +744,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
}
deferred.resolve(result);
- }, function fail(response) {
- deferred.reject(response.data);
+ }, function fail() {
+ deferred.reject();
});
return deferred.promise;
}
- function checkEntityAlias(entityAlias) {
- var deferred = $q.defer();
- var entityType = entityAlias.entityType;
- var entityFilter = entityAlias.entityFilter;
- var promise;
- if (entityFilter.useFilter) {
- var entityNameFilter = entityFilter.entityNameFilter;
- promise = getEntitiesByNameFilter(entityType, entityNameFilter, 1);
- } else {
- var entityList = entityFilter.entityList;
- promise = getEntities(entityType, entityList);
- }
- promise.then(
- function success(entities) {
- if (entities && entities.length > 0) {
- deferred.resolve(true);
- } else {
- deferred.resolve(false);
- }
- },
- function fail() {
- deferred.resolve(false);
- }
- );
- return deferred.promise;
- }
-
- function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) {
+ function createDatasourcesFromSubscriptionsInfo(subscriptionsInfo) {
var deferred = $q.defer();
var datasources = [];
processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
@@ -822,4 +1185,4 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
}
-}
\ No newline at end of file
+}
ui/src/app/api/entity-relation.service.js 14(+13 -1)
diff --git a/ui/src/app/api/entity-relation.service.js b/ui/src/app/api/entity-relation.service.js
index 875b2fa..351c252 100644
--- a/ui/src/app/api/entity-relation.service.js
+++ b/ui/src/app/api/entity-relation.service.js
@@ -30,7 +30,8 @@ function EntityRelationService($http, $q) {
findByTo: findByTo,
findInfoByTo: findInfoByTo,
findByToAndType: findByToAndType,
- findByQuery: findByQuery
+ findByQuery: findByQuery,
+ findInfoByQuery: findInfoByQuery
}
return service;
@@ -159,4 +160,15 @@ function EntityRelationService($http, $q) {
return deferred.promise;
}
+ function findInfoByQuery(query) {
+ var deferred = $q.defer();
+ var url = '/api/relations/info';
+ $http.post(url, query).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/subscription.js 207(+102 -105)
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index fb774a1..1d4eb30 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -39,6 +39,9 @@ export default class Subscription {
this.cafs = {};
this.registrations = [];
+ var subscription = this;
+ var deferred = this.ctx.$q.defer();
+
if (this.type === this.ctx.types.widgetType.rpc.value) {
this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
@@ -56,7 +59,11 @@ export default class Subscription {
this.rpcEnabled = false;
this.executingRpcRequest = false;
this.executingPromises = [];
- this.initRpc();
+ this.initRpc().then(
+ function() {
+ deferred.resolve(subscription);
+ }
+ );
} else {
this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
@@ -66,6 +73,15 @@ export default class Subscription {
this.datasources = this.ctx.utils.validateDatasources(options.datasources);
this.datasourceListeners = [];
+
+ /*
+ * data = array of datasourceData
+ * datasourceData = {
+ * tbDatasource,
+ * dataKey, { name, config }
+ * data = array of [time, value]
+ * }
+ */
this.data = [];
this.hiddenData = [];
this.originalTimewindow = null;
@@ -103,11 +119,41 @@ export default class Subscription {
this.legendConfig.showMax === true ||
this.legendConfig.showAvg === true ||
this.legendConfig.showTotal === true);
- this.initDataSubscription();
+ this.initDataSubscription().then(
+ function success() {
+ deferred.resolve(subscription);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
}
+
+ return deferred.promise;
}
initDataSubscription() {
+ var deferred = this.ctx.$q.defer();
+ if (!this.ctx.aliasController) {
+ this.configureData();
+ deferred.resolve();
+ } else {
+ var subscription = this;
+ this.ctx.aliasController.resolveDatasources(this.datasources).then(
+ function success(datasources) {
+ subscription.datasources = datasources;
+ subscription.configureData();
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ configureData() {
var dataIndex = 0;
for (var i = 0; i < this.datasources.length; i++) {
var datasource = this.datasources[i];
@@ -199,21 +245,46 @@ export default class Subscription {
}
initRpc() {
+ var deferred = this.ctx.$q.defer();
if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
- if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
- this.targetDeviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
+ var subscription = this;
+ this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).then(
+ function success(aliasInfo) {
+ if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
+ subscription.targetDeviceId = aliasInfo.currentEntity.id;
+ if (subscription.targetDeviceId) {
+ subscription.rpcEnabled = true;
+ } else {
+ subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
+ }
+ subscription.callbacks.rpcStateChanged(subscription);
+ deferred.resolve();
+ } else {
+ subscription.rpcEnabled = false;
+ subscription.callbacks.rpcStateChanged(subscription);
+ deferred.resolve();
+ }
+ },
+ function fail () {
+ subscription.rpcEnabled = false;
+ subscription.callbacks.rpcStateChanged(subscription);
+ deferred.resolve();
+ }
+ );
+ } else {
+ if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
+ this.targetDeviceId = this.targetDeviceIds[0];
}
- } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
- this.targetDeviceId = this.targetDeviceIds[0];
- }
-
- if (this.targetDeviceId) {
- this.rpcEnabled = true;
- } else {
- this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
+ if (this.targetDeviceId) {
+ this.rpcEnabled = true;
+ } else {
+ this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
+ }
+ this.callbacks.rpcStateChanged(this);
+ deferred.resolve();
}
- this.callbacks.rpcStateChanged(this);
+ return deferred.promise;
}
clearRpcError() {
@@ -319,11 +390,11 @@ export default class Subscription {
this.onDataUpdated();
}
- onAliasesChanged() {
+ onAliasesChanged(aliasIds) {
if (this.type === this.ctx.types.widgetType.rpc.value) {
- this.checkRpcTarget();
+ return this.checkRpcTarget(aliasIds);
} else {
- this.checkSubscriptions();
+ return this.checkSubscriptions(aliasIds);
}
}
@@ -481,39 +552,6 @@ export default class Subscription {
var datasource = this.datasources[i];
if (angular.isFunction(datasource))
continue;
- var entityId = null;
- var entityType = null;
- if (datasource.type === this.ctx.types.datasourceType.entity) {
- var aliasName = null;
- var entityName = null;
- if (datasource.entityId) {
- entityId = datasource.entityId;
- entityType = datasource.entityType;
- datasource.name = datasource.entityName;
- aliasName = datasource.entityName;
- entityName = datasource.entityName;
- } else if (datasource.entityAliasId) {
- if (this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId]) {
- entityId = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityId;
- entityType = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].entityType;
- datasource.name = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
- aliasName = this.ctx.aliasesInfo.entityAliases[datasource.entityAliasId].alias;
- entityName = '';
- var entitiesInfo = this.ctx.aliasesInfo.entityAliasesInfo[datasource.entityAliasId];
- for (var d = 0; d < entitiesInfo.length; d++) {
- if (entitiesInfo[d].id === entityId) {
- entityName = entitiesInfo[d].name;
- break;
- }
- }
- }
- }
- } else {
- datasource.name = datasource.name || this.ctx.types.datasourceType.function;
- }
- for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
- updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, entityName, aliasName);
- }
var subscription = this;
@@ -521,8 +559,8 @@ export default class Subscription {
subscriptionType: this.type,
subscriptionTimewindow: this.subscriptionTimewindow,
datasource: datasource,
- entityType: entityType,
- entityId: entityId,
+ entityType: datasource.entityType,
+ entityId: datasource.entityId,
dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
},
@@ -544,6 +582,10 @@ export default class Subscription {
this.datasourceListeners.push(listener);
this.ctx.datasourceService.subscribeToDatasource(listener);
+ if (datasource.unresolvedStateEntity) {
+ this.notifyDataLoaded();
+ this.onDataUpdated();
+ }
}
}
@@ -557,48 +599,26 @@ export default class Subscription {
}
}
- checkRpcTarget() {
- var deviceId = null;
- if (this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId]) {
- deviceId = this.ctx.aliasesInfo.entityAliases[this.targetDeviceAliasId].entityId;
- }
- if (!angular.equals(deviceId, this.targetDeviceId)) {
- this.targetDeviceId = deviceId;
- if (this.targetDeviceId) {
- this.rpcEnabled = true;
- } else {
- this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
- }
- this.callbacks.rpcStateChanged(this);
+ checkRpcTarget(aliasIds) {
+ if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {
+ return true;
+ } else {
+ return false;
}
}
- checkSubscriptions() {
+ checkSubscriptions(aliasIds) {
var subscriptionsChanged = false;
for (var i = 0; i < this.datasourceListeners.length; i++) {
var listener = this.datasourceListeners[i];
- var entityId = null;
- var entityType = null;
- var aliasName = null;
- if (listener.datasource.type === this.ctx.types.datasourceType.entity) {
- if (listener.datasource.entityAliasId &&
- this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId]) {
- entityId = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityId;
- entityType = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].entityType;
- aliasName = this.ctx.aliasesInfo.entityAliases[listener.datasource.entityAliasId].alias;
- }
- if (!angular.equals(entityId, listener.entityId) ||
- !angular.equals(entityType, listener.entityType) ||
- !angular.equals(aliasName, listener.datasource.name)) {
+ if (listener.datasource.entityAliasId) {
+ if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
subscriptionsChanged = true;
break;
}
}
}
- if (subscriptionsChanged) {
- this.unsubscribe();
- this.subscribe();
- }
+ return subscriptionsChanged;
}
destroy() {
@@ -617,29 +637,6 @@ export default class Subscription {
}
-const varsRegex = /\$\{([^\}]*)\}/g;
-
-function updateDataKeyLabel(dataKey, dsName, entityName, aliasName) {
- var pattern = dataKey.pattern;
- var label = dataKey.pattern;
- var match = varsRegex.exec(pattern);
- while (match !== null) {
- var variable = match[0];
- var variableName = match[1];
- if (variableName === 'dsName') {
- label = label.split(variable).join(dsName);
- } else if (variableName === 'entityName') {
- label = label.split(variable).join(entityName);
- } else if (variableName === 'deviceName') {
- label = label.split(variable).join(entityName);
- } else if (variableName === 'aliasName') {
- label = label.split(variable).join(aliasName);
- }
- match = varsRegex.exec(pattern);
- }
- dataKey.label = label;
-}
-
function calculateMin(data) {
if (data.length > 0) {
var result = Number(data[0][1]);
ui/src/app/asset/assets.tpl.html 7(+6 -1)
diff --git a/ui/src/app/asset/assets.tpl.html b/ui/src/app/asset/assets.tpl.html
index 11a118f..8370d3d 100644
--- a/ui/src/app/asset/assets.tpl.html
+++ b/ui/src/app/asset/assets.tpl.html
@@ -48,11 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.asset"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'asset.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.asset"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
ui/src/app/asset/index.js 2(+0 -2)
diff --git a/ui/src/app/asset/index.js b/ui/src/app/asset/index.js
index 62ab201..2426e85 100644
--- a/ui/src/app/asset/index.js
+++ b/ui/src/app/asset/index.js
@@ -15,7 +15,6 @@
*/
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
-import thingsboardEvent from '../event';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiAsset from '../api/asset.service';
import thingsboardApiCustomer from '../api/customer.service';
@@ -29,7 +28,6 @@ import AssetDirective from './asset.directive';
export default angular.module('thingsboard.asset', [
uiRouter,
thingsboardGrid,
- thingsboardEvent,
thingsboardApiUser,
thingsboardApiAsset,
thingsboardApiCustomer
ui/src/app/common/dashboard-utils.service.js 148(+125 -23)
diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js
index 78136df..915d550 100644
--- a/ui/src/app/common/dashboard-utils.service.js
+++ b/ui/src/app/common/dashboard-utils.service.js
@@ -23,8 +23,10 @@ function DashboardUtils(types, utils, timeService) {
var service = {
validateAndUpdateDashboard: validateAndUpdateDashboard,
+ validateAndUpdateWidget: validateAndUpdateWidget,
getRootStateId: getRootStateId,
createSingleWidgetDashboard: createSingleWidgetDashboard,
+ createSingleEntityFilter: createSingleEntityFilter,
getStateLayoutsData: getStateLayoutsData,
createDefaultState: createDefaultState,
createDefaultLayoutData: createDefaultLayoutData,
@@ -39,40 +41,104 @@ function DashboardUtils(types, utils, timeService) {
return service;
- function validateAndUpdateEntityAliases(configuration) {
+ function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
+ var aliasId, entityAlias;
if (angular.isUndefined(configuration.entityAliases)) {
configuration.entityAliases = {};
if (configuration.deviceAliases) {
var deviceAliases = configuration.deviceAliases;
- for (var aliasId in deviceAliases) {
+ for (aliasId in deviceAliases) {
var deviceAlias = deviceAliases[aliasId];
- var alias = deviceAlias.alias;
- var entityFilter = {
- useFilter: false,
- entityNameFilter: '',
- entityList: []
- }
- if (deviceAlias.deviceFilter) {
- entityFilter.useFilter = deviceAlias.deviceFilter.useFilter;
- entityFilter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
- entityFilter.entityList = deviceAlias.deviceFilter.deviceList;
- } else if (deviceAlias.deviceId) {
- entityFilter.entityList = [deviceAlias.deviceId];
- }
- var entityAlias = {
- id: aliasId,
- alias: alias,
- entityType: types.entityType.device,
- entityFilter: entityFilter
- };
- configuration.entityAliases[aliasId] = entityAlias;
+ entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId);
+ configuration.entityAliases[entityAlias.id] = entityAlias;
}
delete configuration.deviceAliases;
}
+ } else {
+ var entityAliases = configuration.entityAliases;
+ for (aliasId in entityAliases) {
+ entityAlias = entityAliases[aliasId];
+ entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId);
+ if (aliasId != entityAlias.id) {
+ delete entityAliases[aliasId];
+ }
+ entityAliases[entityAlias.id] = entityAlias;
+ }
}
return configuration;
}
+ function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) {
+ if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) {
+ var newAliasId = utils.guid();
+ var aliasDatasources = datasourcesByAliasId[aliasId];
+ if (aliasDatasources) {
+ aliasDatasources.forEach(
+ function(datasource) {
+ datasource.entityAliasId = newAliasId;
+ }
+ );
+ }
+ var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
+ if (targetDeviceAliasIdsList) {
+ targetDeviceAliasIdsList.forEach(
+ function(targetDeviceAliasIds) {
+ targetDeviceAliasIds[0] = newAliasId;
+ }
+ );
+ }
+ return newAliasId;
+ } else {
+ return aliasId;
+ }
+ }
+
+ function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) {
+ aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
+ var alias = deviceAlias.alias;
+ var entityAlias = {
+ id: aliasId,
+ alias: alias,
+ filter: {
+ type: null,
+ entityType: types.entityType.device,
+ resolveMultiple: false
+ },
+ }
+ if (deviceAlias.deviceFilter) {
+ entityAlias.filter.type =
+ deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
+ if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
+ entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
+ } else {
+ entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
+ }
+ } else {
+ entityAlias.filter.type = types.aliasFilterType.entityList.value;
+ entityAlias.filter.entityList = [deviceAlias.deviceId];
+ }
+ return entityAlias;
+ }
+
+ function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) {
+ entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
+ if (!entityAlias.filter) {
+ entityAlias.filter = {
+ type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
+ entityType: entityAlias.entityType,
+ resolveMultiple: false
+ }
+ if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
+ entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
+ } else {
+ entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
+ }
+ delete entityAlias.entityType;
+ delete entityAlias.entityFilter;
+ }
+ return entityAlias;
+ }
+
function validateAndUpdateWidget(widget) {
if (!widget.config) {
widget.config = {};
@@ -166,7 +232,34 @@ function DashboardUtils(types, utils, timeService) {
states[firstStateId].root = true;
}
}
- dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration);
+
+ var datasourcesByAliasId = {};
+ var targetDevicesByAliasId = {};
+ for (var widgetId in dashboard.configuration.widgets) {
+ widget = dashboard.configuration.widgets[widgetId];
+ widget.config.datasources.forEach(function (datasource) {
+ if (datasource.entityAliasId) {
+ var aliasId = datasource.entityAliasId;
+ var aliasDatasources = datasourcesByAliasId[aliasId];
+ if (!aliasDatasources) {
+ aliasDatasources = [];
+ datasourcesByAliasId[aliasId] = aliasDatasources;
+ }
+ aliasDatasources.push(datasource);
+ }
+ });
+ if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) {
+ var aliasId = widget.config.targetDeviceAliasIds[0];
+ var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
+ if (!targetDeviceAliasIdsList) {
+ targetDeviceAliasIdsList = [];
+ targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList;
+ }
+ targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds);
+ }
+ }
+
+ dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId);
if (angular.isUndefined(dashboard.configuration.timewindow)) {
dashboard.configuration.timewindow = timeService.defaultTimewindow();
@@ -243,6 +336,15 @@ function DashboardUtils(types, utils, timeService) {
return dashboard;
}
+ function createSingleEntityFilter(entityType, entityId) {
+ return {
+ type: types.aliasFilterType.entityList.value,
+ entityList: [entityId],
+ entityType: entityType,
+ resolveMultiple: false
+ };
+ }
+
function getStateLayoutsData(dashboard, targetState) {
var dashboardConfiguration = dashboard.configuration;
var states = dashboardConfiguration.states;
ui/src/app/common/types.constant.js 123(+119 -4)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 44e59a2..b281ad1 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -65,6 +65,69 @@ export default angular.module('thingsboard.types', [])
clearedUnack: "CLEARED_UNACK",
clearedAck: "CLEARED_ACK"
},
+ alarmSearchStatus: {
+ any: "ANY",
+ active: "ACTIVE",
+ cleared: "CLEARED",
+ ack: "ACK",
+ unack: "UNACK"
+ },
+ alarmSeverity: {
+ "CRITICAL": {
+ name: "alarm.severity-critical",
+ class: "tb-critical"
+ },
+ "MAJOR": {
+ name: "alarm.severity-major",
+ class: "tb-major"
+ },
+ "MINOR": {
+ name: "alarm.severity-minor",
+ class: "tb-minor"
+ },
+ "WARNING": {
+ name: "alarm.severity-warning",
+ class: "tb-warning"
+ },
+ "INDETERMINATE": {
+ name: "alarm.severity-indeterminate",
+ class: "tb-indeterminate"
+ }
+ },
+ aliasFilterType: {
+ entityList: {
+ value: 'entityList',
+ name: 'alias.filter-type-entity-list'
+ },
+ entityName: {
+ value: 'entityName',
+ name: 'alias.filter-type-entity-name'
+ },
+ stateEntity: {
+ value: 'stateEntity',
+ name: 'alias.filter-type-state-entity'
+ },
+ assetType: {
+ value: 'assetType',
+ name: 'alias.filter-type-asset-type'
+ },
+ deviceType: {
+ value: 'deviceType',
+ name: 'alias.filter-type-device-type'
+ },
+ relationsQuery: {
+ value: 'relationsQuery',
+ name: 'alias.filter-type-relations-query'
+ },
+ assetSearchQuery: {
+ value: 'assetSearchQuery',
+ name: 'alias.filter-type-asset-search-query'
+ },
+ deviceSearchQuery: {
+ value: 'deviceSearchQuery',
+ name: 'alias.filter-type-device-search-query'
+ }
+ },
position: {
top: {
value: "top",
@@ -109,6 +172,62 @@ export default angular.module('thingsboard.types', [])
dashboard: "DASHBOARD",
alarm: "ALARM"
},
+ entityTypeTranslations: {
+ "DEVICE": {
+ type: 'entity.type-device',
+ typePlural: 'entity.type-devices',
+ list: 'entity.list-of-devices',
+ nameStartsWith: 'entity.device-name-starts-with'
+ },
+ "ASSET": {
+ type: 'entity.type-asset',
+ typePlural: 'entity.type-assets',
+ list: 'entity.list-of-assets',
+ nameStartsWith: 'entity.asset-name-starts-with'
+ },
+ "RULE": {
+ type: 'entity.type-rule',
+ typePlural: 'entity.type-rules',
+ list: 'entity.list-of-rules',
+ nameStartsWith: 'entity.rule-name-starts-with'
+ },
+ "PLUGIN": {
+ type: 'entity.type-plugin',
+ typePlural: 'entity.type-plugins',
+ list: 'entity.list-of-plugins',
+ nameStartsWith: 'entity.plugin-name-starts-with'
+ },
+ "TENANT": {
+ type: 'entity.type-tenant',
+ typePlural: 'entity.type-tenants',
+ list: 'entity.list-of-tenants',
+ nameStartsWith: 'entity.tenant-name-starts-with'
+ },
+ "CUSTOMER": {
+ type: 'entity.type-customer',
+ typePlural: 'entity.type-customers',
+ list: 'entity.list-of-customers',
+ nameStartsWith: 'entity.customer-name-starts-with'
+ },
+ "USER": {
+ type: 'entity.type-user',
+ typePlural: 'entity.type-users',
+ list: 'entity.list-of-users',
+ nameStartsWith: 'entity.user-name-starts-with'
+ },
+ "DASHBOARD": {
+ type: 'entity.type-dashboard',
+ typePlural: 'entity.type-dashboards',
+ list: 'entity.list-of-dashboards',
+ nameStartsWith: 'entity.dashboard-name-starts-with'
+ },
+ "ALARM": {
+ type: 'entity.type-alarm',
+ typePlural: 'entity.type-alarms',
+ list: 'entity.list-of-alarms',
+ nameStartsWith: 'entity.alarm-name-starts-with'
+ }
+ },
entitySearchDirection: {
from: "FROM",
to: "TO"
@@ -118,10 +237,6 @@ export default angular.module('thingsboard.types', [])
manages: "Manages"
},
eventType: {
- alarm: {
- value: "ALARM",
- name: "event.type-alarm"
- },
error: {
value: "ERROR",
name: "event.type-error"
ui/src/app/common/utils.service.js 37(+12 -25)
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index 7b4d65e..8d2e565 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -106,10 +106,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
filterSearchTextEntities: filterSearchTextEntities,
guid: guid,
+ cleanCopy: cleanCopy,
isLocalUrl: isLocalUrl,
validateDatasources: validateDatasources,
- createKey: createKey,
- entityTypeName: entityTypeName
+ createKey: createKey
}
return service;
@@ -291,6 +291,16 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
s4() + '-' + s4() + s4() + s4();
}
+ function cleanCopy(object) {
+ var copy = angular.copy(object);
+ for (var prop in copy) {
+ if (prop && prop.startsWith('$$')) {
+ delete copy[prop];
+ }
+ }
+ return copy;
+ }
+
function genNextColor(datasources) {
var index = 0;
if (datasources) {
@@ -347,27 +357,4 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
return dataKey;
}
- function entityTypeName (type) {
- switch (type) {
- case types.entityType.device:
- return 'entity.type-device';
- case types.entityType.asset:
- return 'entity.type-asset';
- case types.entityType.rule:
- return 'entity.type-rule';
- case types.entityType.plugin:
- return 'entity.type-plugin';
- case types.entityType.tenant:
- return 'entity.type-tenant';
- case types.entityType.customer:
- return 'entity.type-customer';
- case types.entityType.user:
- return 'entity.type-user';
- case types.entityType.dashboard:
- return 'entity.type-dashboard';
- case types.entityType.alarm:
- return 'entity.type-alarm';
- }
- }
-
}
ui/src/app/components/dashboard.directive.js 37(+23 -14)
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index f26121c..a88e656 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -52,7 +52,7 @@ function Dashboard() {
bindToController: {
widgets: '=',
widgetLayouts: '=?',
- aliasesInfo: '=',
+ aliasController: '=',
stateController: '=',
dashboardTimewindow: '=?',
columns: '=',
@@ -85,7 +85,7 @@ function Dashboard() {
}
/*@ngInject*/
-function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types, utils) {
+function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $mdUtil, timeService, types, utils) {
var highlightedMode = false;
var highlightedWidget = null;
@@ -329,10 +329,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
$scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
});
- $scope.$watch('vm.aliasesInfo.entityAliases', function () {
- $scope.$broadcast('entityAliasListChanged', vm.aliasesInfo);
- }, true);
-
$scope.$on('gridster-resized', function (event, sizes, theGridster) {
if (checkIsLocalGridsterElement(theGridster)) {
vm.gridster = theGridster;
@@ -796,7 +792,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}
function dashboardLoaded() {
- $timeout(function () {
+ $mdUtil.nextTick(function () {
if (vm.dashboardTimewindowWatch) {
vm.dashboardTimewindowWatch();
vm.dashboardTimewindowWatch = null;
@@ -806,14 +802,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
}, true);
adoptMaxRows();
vm.dashboardLoading = false;
- $timeout(function () {
- var gridsterScope = gridsterElement.scope();
- vm.gridster = gridsterScope.gridster;
- if (vm.onInit) {
- vm.onInit({dashboard: vm});
+ if ($scope.gridsterScopeWatcher) {
+ $scope.gridsterScopeWatcher();
+ }
+ $scope.gridsterScopeWatcher = $scope.$watch(
+ function() {
+ var hasScope = gridsterElement.scope() ? true : false;
+ return hasScope;
+ },
+ function(hasScope) {
+ if (hasScope) {
+ $scope.gridsterScopeWatcher();
+ $scope.gridsterScopeWatcher = null;
+ var gridsterScope = gridsterElement.scope();
+ vm.gridster = gridsterScope.gridster;
+ if (vm.onInit) {
+ vm.onInit({dashboard: vm});
+ }
+ }
}
- }, 0, false);
- }, 0, false);
+ );
+ });
}
function loading() {
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 69934ac..8d77a6f 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -89,7 +89,7 @@
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect,
widget: widget,
- aliasesInfo: vm.aliasesInfo,
+ aliasController: vm.aliasController,
stateController: vm.stateController,
isEdit: vm.isEdit,
stDiff: vm.stDiff,
diff --git a/ui/src/app/components/datakey-config-dialog.controller.js b/ui/src/app/components/datakey-config-dialog.controller.js
index a8741f5..ccaac21 100644
--- a/ui/src/app/components/datakey-config-dialog.controller.js
+++ b/ui/src/app/components/datakey-config-dialog.controller.js
@@ -20,14 +20,14 @@ export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [things
.name;
/*@ngInject*/
-function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey, dataKeySettingsSchema, entityAlias, entityAliases) {
+function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dataKey, dataKeySettingsSchema, entityAlias, aliasController) {
var vm = this;
vm.dataKey = dataKey;
vm.dataKeySettingsSchema = dataKeySettingsSchema;
vm.entityAlias = entityAlias;
- vm.entityAliases = entityAliases;
+ vm.aliasController = aliasController;
vm.hide = function () {
$mdDialog.hide();
@@ -38,12 +38,28 @@ function DatakeyConfigDialogController($scope, $mdDialog, entityService, dataKey
};
vm.fetchEntityKeys = function (entityAliasId, query, type) {
- var alias = vm.entityAliases[entityAliasId];
- if (alias) {
- return entityService.getEntityKeys(alias.entityType, alias.entityId, query, type);
- } else {
- return [];
- }
+ var deferred = $q.defer();
+ vm.aliasController.getAliasInfo(entityAliasId).then(
+ function success(aliasInfo) {
+ var entity = aliasInfo.currentEntity;
+ if (entity) {
+ entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
+ function success(keys) {
+ deferred.resolve(keys);
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ } else {
+ deferred.resolve([]);
+ }
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ return deferred.promise;
};
vm.save = function () {
diff --git a/ui/src/app/components/datasource.directive.js b/ui/src/app/components/datasource.directive.js
index 2c06a38..eb9eafc 100644
--- a/ui/src/app/components/datasource.directive.js
+++ b/ui/src/app/components/datasource.directive.js
@@ -76,7 +76,7 @@ function Datasource($compile, $templateCache, types) {
restrict: "E",
require: "^ngModel",
scope: {
- entityAliases: '=',
+ aliasController: '=',
widgetType: '=',
functionsOnly: '=',
datakeySettingsSchema: '=',
diff --git a/ui/src/app/components/datasource.tpl.html b/ui/src/app/components/datasource.tpl.html
index 88ff247..0e91d98 100644
--- a/ui/src/app/components/datasource.tpl.html
+++ b/ui/src/app/components/datasource.tpl.html
@@ -37,7 +37,7 @@
ng-switch-when="entity"
ng-required="model.type === types.datasourceType.entity"
widget-type="widgetType"
- entity-aliases="entityAliases"
+ alias-controller="aliasController"
generate-data-key="generateDataKey({chip: chip, type: type})"
fetch-entity-keys="fetchEntityKeys({entityAliasId: entityAliasId, query: query, type: type})"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js
index 97732b9..da43274 100644
--- a/ui/src/app/components/datasource-entity.directive.js
+++ b/ui/src/app/components/datasource-entity.directive.js
@@ -103,10 +103,9 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var entityAliasId = ngModelCtrl.$viewValue.entityAliasId;
- if (scope.entityAliases[entityAliasId]) {
- scope.entityAlias = {id: entityAliasId, alias: scope.entityAliases[entityAliasId].alias,
- entityType: scope.entityAliases[entityAliasId].entityType,
- entityId: scope.entityAliases[entityAliasId].entityId};
+ var entityAliases = scope.aliasController.getEntityAliases();
+ if (entityAliases[entityAliasId]) {
+ scope.entityAlias = entityAliases[entityAliasId];
} else {
scope.entityAlias = null;
}
@@ -182,7 +181,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
dataKey: angular.copy(dataKey),
dataKeySettingsSchema: scope.datakeySettingsSchema,
entityAlias: scope.entityAlias,
- entityAliases: scope.entityAliases
+ aliasController: scope.aliasController
},
parent: angular.element($document[0].body),
fullscreen: true,
@@ -236,7 +235,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
require: "^ngModel",
scope: {
widgetType: '=',
- entityAliases: '=',
+ aliasController: '=',
datakeySettingsSchema: '=',
generateDataKey: '&',
fetchEntityKeys: '&',
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index 97ee1b5..2fb4608 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -18,7 +18,7 @@
<section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<tb-entity-alias-select
tb-required="true"
- entity-aliases="entityAliases"
+ alias-controller="aliasController"
ng-model="entityAlias"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias})">
</tb-entity-alias-select>
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index 0f66dca..fe54696 100644
--- a/ui/src/app/components/datasource-func.directive.js
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -71,6 +71,13 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
}
}, true);
+ scope.$watch('datasourceName', function () {
+ if (ngModelCtrl.$viewValue) {
+ ngModelCtrl.$viewValue.name = scope.datasourceName;
+ scope.updateValidity();
+ }
+ });
+
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
var funcDataKeys = [];
@@ -78,6 +85,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
}
scope.funcDataKeys = funcDataKeys;
+ scope.datasourceName = ngModelCtrl.$viewValue.name;
}
};
ui/src/app/components/datasource-func.scss 38(+22 -16)
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss
index 1a5dcc7..8739ed8 100644
--- a/ui/src/app/components/datasource-func.scss
+++ b/ui/src/app/components/datasource-func.scss
@@ -15,23 +15,29 @@
*/
@import '../../scss/constants';
-.tb-func-datakey-autocomplete {
- .tb-not-found {
- display: block;
- line-height: 1.5;
- height: 48px;
- .tb-no-entries {
- line-height: 48px;
- }
+.tb-datasource-func {
+ @media (min-width: $layout-breakpoint-gt-sm) {
+ padding-left: 8px;
}
- li {
- height: auto !important;
- white-space: normal !important;
+
+ md-input-container.tb-datasource-name {
+ .md-errors-spacer {
+ display: none;
+ }
}
-}
-tb-datasource-func {
- @media (min-width: $layout-breakpoint-gt-sm) {
- padding-left: 8px;
+ .tb-func-datakey-autocomplete {
+ .tb-not-found {
+ display: block;
+ line-height: 1.5;
+ height: 48px;
+ .tb-no-entries {
+ line-height: 48px;
+ }
+ }
+ li {
+ height: auto !important;
+ white-space: normal !important;
+ }
}
-}
\ No newline at end of file
+}
ui/src/app/components/datasource-func.tpl.html 111(+60 -51)
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
index 1853b9d..e8e08f7 100644
--- a/ui/src/app/components/datasource-func.tpl.html
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -15,59 +15,68 @@
limitations under the License.
-->
-<section flex layout='column' style="padding-left: 4px;">
- <md-chips flex
- id="function_datakey_chips"
- ng-required="true"
- ng-model="funcDataKeys" md-autocomplete-snap
- md-transform-chip="transformDataKeyChip($chip)"
- md-require-match="false">
- <md-autocomplete
- md-no-cache="false"
- id="dataKey"
- md-selected-item="selectedDataKey"
- md-search-text="dataKeySearchText"
- md-items="item in dataKeysSearch(dataKeySearchText)"
- md-item-text="item.name"
- md-min-length="0"
- placeholder="{{ 'datakey.function-types' | translate }}"
- md-menu-class="tb-func-datakey-autocomplete">
- <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
- <md-not-found>
- <div class="tb-not-found">
- <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
- <span translate>device.no-keys-found</span>
+<section class="tb-datasource-func" flex layout='column'
+ layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
+ <md-input-container class="tb-datasource-name" md-no-float style="min-width: 200px;">
+ <input name="datasourceName"
+ placeholder="{{ 'datasource.name' | translate }}"
+ ng-model="datasourceName"
+ aria-label="{{ 'datasource.name' | translate }}">
+ </md-input-container>
+ <section flex layout='column' style="padding-left: 4px;">
+ <md-chips flex
+ id="function_datakey_chips"
+ ng-required="true"
+ ng-model="funcDataKeys" md-autocomplete-snap
+ md-transform-chip="transformDataKeyChip($chip)"
+ md-require-match="false">
+ <md-autocomplete
+ md-no-cache="false"
+ id="dataKey"
+ md-selected-item="selectedDataKey"
+ md-search-text="dataKeySearchText"
+ md-items="item in dataKeysSearch(dataKeySearchText)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{ 'datakey.function-types' | translate }}"
+ md-menu-class="tb-func-datakey-autocomplete">
+ <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+ <md-not-found>
+ <div class="tb-not-found">
+ <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
+ <span translate>device.no-keys-found</span>
+ </div>
+ <div ng-if="textIsNotEmpty(dataKeySearchText)">
+ <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span>
+ <span>
+ <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
+ </span>
+ </div>
</div>
- <div ng-if="textIsNotEmpty(dataKeySearchText)">
- <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span>
- <span>
- <a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
- </span>
- </div>
- </div>
- </md-not-found>
- </md-autocomplete>
- <md-chip-template>
- <div layout="row" layout-align="start center" class="tb-attribute-chip">
- <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
- <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
- </div>
- <div layout="row" flex>
- <div class="tb-chip-label">
- {{$chip.label}}
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <div layout="row" layout-align="start center" class="tb-attribute-chip">
+ <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+ <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div class="tb-chip-separator">: </div>
- <div class="tb-chip-label">
- <strong>{{$chip.name}}</strong>
+ <div layout="row" flex>
+ <div class="tb-chip-label">
+ {{$chip.label}}
+ </div>
+ <div class="tb-chip-separator">: </div>
+ <div class="tb-chip-label">
+ <strong>{{$chip.name}}</strong>
+ </div>
</div>
+ <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+ <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+ </md-button>
</div>
- <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
- <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
- </md-button>
- </div>
- </md-chip-template>
- </md-chips>
- <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
- <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
- </div>
+ </md-chip-template>
+ </md-chips>
+ <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
+ <div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
+ </div>
+ </section>
</section>
diff --git a/ui/src/app/components/entity-alias-select.directive.js b/ui/src/app/components/entity-alias-select.directive.js
index 204b83f..ca2db85 100644
--- a/ui/src/app/components/entity-alias-select.directive.js
+++ b/ui/src/app/components/entity-alias-select.directive.js
@@ -31,7 +31,7 @@ export default angular.module('thingsboard.directives.entityAliasSelect', [])
.name;
/*@ngInject*/
-function EntityAliasSelect($compile, $templateCache, $mdConstant) {
+function EntityAliasSelect($compile, $templateCache, $mdConstant, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityAliasSelectTemplate);
@@ -49,19 +49,18 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
ngModelCtrl.$setValidity('entityAlias', valid);
};
- scope.$watch('entityAliases', function () {
+ scope.$watch('aliasController', function () {
scope.entityAliasList = [];
- for (var aliasId in scope.entityAliases) {
+ var entityAliases = scope.aliasController.getEntityAliases();
+ for (var aliasId in entityAliases) {
if (scope.allowedEntityTypes) {
- if (scope.allowedEntityTypes.indexOf(scope.entityAliases[aliasId].entityType) === -1) {
+ if (!entityService.filterAliasByEntityTypes(entityAliases[aliasId], scope.allowedEntityTypes)) {
continue;
}
}
- var entityAlias = {id: aliasId, alias: scope.entityAliases[aliasId].alias,
- entityType: scope.entityAliases[aliasId].entityType, entityId: scope.entityAliases[aliasId].entityId};
- scope.entityAliasList.push(entityAlias);
+ scope.entityAliasList.push(entityAliases[aliasId]);
}
- }, true);
+ });
scope.$watch('entityAlias', function () {
scope.updateView();
@@ -141,7 +140,7 @@ function EntityAliasSelect($compile, $templateCache, $mdConstant) {
link: linker,
scope: {
tbRequired: '=?',
- entityAliases: '=',
+ aliasController: '=',
allowedEntityTypes: '=?',
onCreateEntityAlias: '&'
}
ui/src/app/components/widget.controller.js 320(+222 -98)
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index 6fc4bba..08885b8 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -20,9 +20,9 @@ import Subscription from '../api/subscription';
/* eslint-disable angular/angularelement */
/*@ngInject*/
-export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, tbRaf, types, utils, timeService,
+export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
datasourceService, entityService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
- dashboardTimewindowApi, widget, aliasesInfo, stateController, widgetType) {
+ dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
var vm = this;
@@ -37,23 +37,16 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.executingRpcRequest = false;
var gridsterItemInited = false;
+ var subscriptionInited = false;
+ var widgetSizeDetected = false;
var cafs = {};
- /*
- * data = array of datasourceData
- * datasourceData = {
- * tbDatasource,
- * dataKey, { name, config }
- * data = array of [time, value]
- * }
- */
-
var widgetContext = {
inited: false,
$scope: $scope,
- $container: $('#container', $element),
- $containerParent: $($element),
+ $container: null,
+ $containerParent: null,
width: 0,
height: 0,
isEdit: isEdit,
@@ -80,30 +73,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
createSubscription: function(options, subscribe) {
return createSubscription(options, subscribe);
},
-
-
- // type: "timeseries" or "latest" or "rpc"
- /* subscriptionInfo = [
- {
- entityType: ""
- entityId: ""
- entityName: ""
- timeseries: [{ name: "", label: "" }, ..]
- attributes: [{ name: "", label: "" }, ..]
- }
- ..
- ]*/
-
- // options = {
- // timeWindowConfig,
- // useDashboardTimewindow,
- // legendConfig,
- // decimals,
- // units,
- // callbacks [ onDataUpdated(subscription, apply) ]
- // }
- //
-
createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe);
},
@@ -149,7 +118,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
dashboardTimewindowApi: dashboardTimewindowApi,
types: types,
stDiff: stDiff,
- aliasesInfo: aliasesInfo
+ aliasController: aliasController
};
var widgetTypeInstance;
@@ -203,23 +172,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
vm.gridsterItemInitialized = gridsterItemInitialized;
- initialize();
-
-
- /*
- options = {
- type,
- targetDeviceAliasIds, // RPC
- targetDeviceIds, // RPC
- datasources,
- timeWindowConfig,
- useDashboardTimewindow,
- legendConfig,
- decimals,
- units,
- callbacks
- }
- */
+ initialize().then(
+ function(){
+ onInit();
+ }
+ );
function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) {
var deferred = $q.defer();
@@ -233,28 +190,42 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
}
- entityService.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then(
+ entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo).then(
function (datasources) {
options.datasources = datasources;
- var subscription = createSubscription(options, subscribe);
- if (useDefaultComponents) {
- defaultSubscriptionOptions(subscription, options);
- }
- deferred.resolve(subscription);
+ createSubscription(options, subscribe).then(
+ function success(subscription) {
+ if (useDefaultComponents) {
+ defaultSubscriptionOptions(subscription, options);
+ }
+ deferred.resolve(subscription);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
}
);
return deferred.promise;
}
function createSubscription(options, subscribe) {
+ var deferred = $q.defer();
options.dashboardTimewindow = dashboardTimewindow;
- var subscription =
- new Subscription(subscriptionContext, options);
- widgetContext.subscriptions[subscription.id] = subscription;
- if (subscribe) {
- subscription.subscribe();
- }
- return subscription;
+ new Subscription(subscriptionContext, options).then(
+ function success(subscription) {
+ widgetContext.subscriptions[subscription.id] = subscription;
+ if (subscribe) {
+ subscription.subscribe();
+ }
+ deferred.resolve(subscription);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+
+ return deferred.promise;
}
function defaultComponentsOptions(options) {
@@ -310,8 +281,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
function createDefaultSubscription() {
- var subscription;
var options;
+ var deferred = $q.defer();
if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
options = {
type: widget.type,
@@ -319,16 +290,23 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
};
defaultComponentsOptions(options);
- subscription = createSubscription(options);
-
- defaultSubscriptionOptions(subscription, options);
+ createSubscription(options).then(
+ function success(subscription) {
+ defaultSubscriptionOptions(subscription, options);
- // backward compatibility
+ // backward compatibility
- widgetContext.datasources = subscription.datasources;
- widgetContext.data = subscription.data;
- widgetContext.hiddenData = subscription.hiddenData;
- widgetContext.timeWindow = subscription.timeWindow;
+ widgetContext.datasources = subscription.datasources;
+ widgetContext.data = subscription.data;
+ widgetContext.hiddenData = subscription.hiddenData;
+ widgetContext.timeWindow = subscription.timeWindow;
+ widgetContext.defaultSubscription = subscription;
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
} else if (widget.type === types.widgetType.rpc.value) {
$scope.loadingData = false;
@@ -356,30 +334,126 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.rpcRejection = null;
}
}
- subscription = createSubscription(options);
+ createSubscription(options).then(
+ function success(subscription) {
+ widgetContext.defaultSubscription = subscription;
+ deferred.resolve();
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
} else if (widget.type === types.widgetType.static.value) {
$scope.loadingData = false;
+ deferred.resolve();
+ } else {
+ deferred.resolve();
}
- if (subscription) {
- widgetContext.defaultSubscription = subscription;
- }
+ return deferred.promise;
}
+ function configureWidgetElement() {
- function initialize() {
+ $scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
+ widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
- if (!vm.useCustomDatasources) {
- createDefaultSubscription();
+ if ($scope.displayLegend) {
+ $scope.legendConfig = widget.config.legendConfig ||
+ {
+ position: types.position.bottom.value,
+ showMin: false,
+ showMax: false,
+ showAvg: widget.type === types.widgetType.timeseries.value,
+ showTotal: false
+ };
+ $scope.legendData = {
+ keys: [],
+ data: []
+ };
+ }
+
+ var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
+ '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
+ '</div>' +
+ '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
+ '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
+ '</div>';
+
+ var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
+ if ($scope.displayLegend) {
+ var layoutType;
+ if ($scope.legendConfig.position === types.position.top.value ||
+ $scope.legendConfig.position === types.position.bottom.value) {
+ layoutType = 'column';
+ } else {
+ layoutType = 'row';
+ }
+
+ var legendStyle;
+ switch($scope.legendConfig.position) {
+ case types.position.top.value:
+ legendStyle = 'padding-bottom: 8px;';
+ break;
+ case types.position.bottom.value:
+ legendStyle = 'padding-top: 8px;';
+ break;
+ case types.position.left.value:
+ legendStyle = 'padding-right: 0px;';
+ break;
+ case types.position.right.value:
+ legendStyle = 'padding-left: 0px;';
+ break;
+ }
+
+ var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
+ containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
+ html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
+ if ($scope.legendConfig.position === types.position.top.value ||
+ $scope.legendConfig.position === types.position.left.value) {
+ html += legendHtml;
+ html += containerHtml;
+ } else {
+ html += containerHtml;
+ html += legendHtml;
+ }
+ html += '</div>';
} else {
- $scope.loadingData = false;
+ html += containerHtml;
}
+ //TODO:
+ /*if (progressElement) {
+ progressScope.$destroy();
+ progressScope = null;
+
+ progressElement.remove();
+ progressElement = null;
+ }*/
+
+ $element.html(html);
+
+ var containerElement = $scope.displayLegend ? angular.element($element[0].querySelector('#widget-container')) : $element;
+ widgetContext.$container = $('#container', containerElement);
+ widgetContext.$containerParent = $(containerElement);
+
+ $compile($element.contents())($scope);
+
+ addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
+ }
+
+ function destroyWidgetElement() {
+ removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
+ $element.html('');
+ widgetContext.$container = null;
+ widgetContext.$containerParent = null;
+ }
+
+ function initialize() {
+
$scope.$on('toggleDashboardEditMode', function (event, isEdit) {
onEditModeChanged(isEdit);
});
- addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
-
$scope.$watch(function () {
return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder;
}, function () {
@@ -398,18 +472,60 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
onMobileModeChanged(newIsMobile);
});
- $scope.$on('entityAliasListChanged', function (event, aliasesInfo) {
- subscriptionContext.aliasesInfo = aliasesInfo;
+ $scope.$on('entityAliasesChanged', function (event, aliasIds) {
+ var subscriptionChanged = false;
for (var id in widgetContext.subscriptions) {
var subscription = widgetContext.subscriptions[id];
- subscription.onAliasesChanged();
+ subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
+ }
+ if (subscriptionChanged && !vm.useCustomDatasources) {
+ reInit();
}
});
$scope.$on("$destroy", function () {
- removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef
onDestroy();
});
+
+ configureWidgetElement();
+ var deferred = $q.defer();
+ if (!vm.useCustomDatasources) {
+ createDefaultSubscription().then(
+ function success() {
+ subscriptionInited = true;
+ deferred.resolve();
+ },
+ function fail() {
+ subscriptionInited = true;
+ deferred.reject();
+ }
+ );
+ } else {
+ $scope.loadingData = false;
+ subscriptionInited = true;
+ deferred.resolve();
+ }
+ return deferred.promise;
+ }
+
+ function reInit() {
+ onDestroy();
+ configureWidgetElement();
+ if (!vm.useCustomDatasources) {
+ createDefaultSubscription().then(
+ function success() {
+ subscriptionInited = true;
+ onInit();
+ },
+ function fail() {
+ subscriptionInited = true;
+ onInit();
+ }
+ );
+ } else {
+ subscriptionInited = true;
+ onInit();
+ }
}
function handleWidgetException(e) {
@@ -417,8 +533,15 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.widgetErrorData = utils.processWidgetException(e);
}
- function onInit() {
- if (!widgetContext.inited) {
+ function isReady() {
+ return subscriptionInited && gridsterItemInited && widgetSizeDetected;
+ }
+
+ function onInit(skipSizeCheck) {
+ if (!skipSizeCheck) {
+ checkSize();
+ }
+ if (!widgetContext.inited && isReady()) {
widgetContext.inited = true;
try {
widgetTypeInstance.onInit();
@@ -443,6 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
widgetContext.width = width;
widgetContext.height = height;
sizeChanged = true;
+ widgetSizeDetected = true;
}
}
return sizeChanged;
@@ -462,8 +586,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
handleWidgetException(e);
}
});
- } else if (gridsterItemInited) {
- onInit();
+ } else {
+ onInit(true);
}
}
}
@@ -472,9 +596,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (item && item.gridster) {
widgetContext.isMobile = item.gridster.isMobile;
gridsterItemInited = true;
- if (checkSize()) {
- onInit();
- }
+ onInit();
// gridsterItemElement = $(item.$element);
//updateVisibility();
}
@@ -544,6 +666,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var subscription = widgetContext.subscriptions[id];
subscription.destroy();
}
+ subscriptionInited = false;
widgetContext.subscriptions = [];
if (widgetContext.inited) {
widgetContext.inited = false;
@@ -559,6 +682,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
handleWidgetException(e);
}
}
+ destroyWidgetElement();
}
//TODO: widgets visibility
ui/src/app/components/widget.directive.js 85(+2 -83)
diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget.directive.js
index c92e544..a0f1b83 100644
--- a/ui/src/app/components/widget.directive.js
+++ b/ui/src/app/components/widget.directive.js
@@ -28,7 +28,7 @@ export default angular.module('thingsboard.directives.widget', [thingsboardLegen
.name;
/*@ngInject*/
-function Widget($controller, $compile, types, widgetService) {
+function Widget($controller, widgetService) {
return {
scope: true,
link: function (scope, elem, attrs) {
@@ -81,90 +81,9 @@ function Widget($controller, $compile, types, widgetService) {
elem.addClass(widgetNamespace);
- var html = '<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
- '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
- '</div>' +
- '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
- '<md-progress-circular md-mode="indeterminate" ng-disabled="!loadingData" class="md-accent" md-diameter="40"></md-progress-circular>' +
- '</div>';
-
- scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
- widget.config.showLegend : widget.type === types.widgetType.timeseries.value;
-
-
- var containerHtml = '<div id="container">' + widgetInfo.templateHtml + '</div>';
- if (scope.displayLegend) {
- scope.legendConfig = widget.config.legendConfig ||
- {
- position: types.position.bottom.value,
- showMin: false,
- showMax: false,
- showAvg: widget.type === types.widgetType.timeseries.value,
- showTotal: false
- };
- scope.legendData = {
- keys: [],
- data: []
- };
-
- var layoutType;
- if (scope.legendConfig.position === types.position.top.value ||
- scope.legendConfig.position === types.position.bottom.value) {
- layoutType = 'column';
- } else {
- layoutType = 'row';
- }
-
- var legendStyle;
- switch(scope.legendConfig.position) {
- case types.position.top.value:
- legendStyle = 'padding-bottom: 8px;';
- break;
- case types.position.bottom.value:
- legendStyle = 'padding-top: 8px;';
- break;
- case types.position.left.value:
- legendStyle = 'padding-right: 0px;';
- break;
- case types.position.right.value:
- legendStyle = 'padding-left: 0px;';
- break;
- }
-
- var legendHtml = '<tb-legend style="'+legendStyle+'" legend-config="legendConfig" legend-data="legendData"></tb-legend>';
- containerHtml = '<div flex id="widget-container">' + containerHtml + '</div>';
- html += '<div class="tb-absolute-fill" layout="'+layoutType+'">';
- if (scope.legendConfig.position === types.position.top.value ||
- scope.legendConfig.position === types.position.left.value) {
- html += legendHtml;
- html += containerHtml;
- } else {
- html += containerHtml;
- html += legendHtml;
- }
- html += '</div>';
- } else {
- html += containerHtml;
- }
-
- //TODO:
- /*if (progressElement) {
- progressScope.$destroy();
- progressScope = null;
-
- progressElement.remove();
- progressElement = null;
- }*/
-
- elem.html(html);
-
- var containerElement = scope.displayLegend ? angular.element(elem[0].querySelector('#widget-container')) : elem;
-
- $compile(elem.contents())(scope);
-
var widgetType = widgetService.getWidgetTypeFunction(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
- angular.extend(locals, {$scope: scope, $element: containerElement, widgetType: widgetType});
+ angular.extend(locals, {$scope: scope, $element: elem, widgetInfo: widgetInfo, widgetType: widgetType});
widgetController = $controller('WidgetController', locals);
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index c3793be..d5b4239 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget-config.directive.js
@@ -128,13 +128,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
} else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
var aliasId = config.targetDeviceAliasIds[0];
- if (scope.entityAliases[aliasId]) {
- scope.targetDeviceAlias.value = {
- id: aliasId,
- alias: scope.entityAliases[aliasId].alias,
- entityType: scope.entityAliases[aliasId].entityType,
- entityId: scope.entityAliases[aliasId].entityId
- };
+ var entityAliases = scope.aliasController.getEntityAliases();
+ if (entityAliases[aliasId]) {
+ scope.targetDeviceAlias.value = entityAliases[aliasId];
} else {
scope.targetDeviceAlias.value = null;
}
@@ -395,7 +391,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
widgetType: '=',
widgetSettingsSchema: '=',
datakeySettingsSchema: '=',
- entityAliases: '=',
+ aliasController: '=',
functionsOnly: '=',
fetchEntityKeys: '&',
onCreateEntityAlias: '&',
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html
index 808e07b..0500e91 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -60,7 +60,7 @@
style="padding: 0 0 0 10px; margin: 5px;">
<tb-datasource flex ng-model="datasource.value"
widget-type="widgetType"
- entity-aliases="entityAliases"
+ alias-controller="aliasController"
functions-only="functionsOnly"
datakey-settings-schema="datakeySettingsSchema"
generate-data-key="generateDataKey(chip,type)"
@@ -104,7 +104,7 @@
<v-pane-content style="padding: 0 5px;">
<tb-entity-alias-select flex
tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
- entity-aliases="entityAliases"
+ alias-controller="aliasController"
allowed-entity-types="[types.entityType.device]"
ng-model="targetDeviceAlias.value"
on-create-entity-alias="onCreateEntityAlias({event: event, alias: alias, allowedEntityTypes: allowedEntityTypes})">
diff --git a/ui/src/app/customer/customers.tpl.html b/ui/src/app/customer/customers.tpl.html
index 6c70a70..a6521f0 100644
--- a/ui/src/app/customer/customers.tpl.html
+++ b/ui/src/app/customer/customers.tpl.html
@@ -48,11 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.customer"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'customer.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.customer"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
ui/src/app/dashboard/add-widget.controller.js 69(+38 -31)
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index 2f23cf9..5372b62 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -15,17 +15,18 @@
*/
/* eslint-disable import/no-unresolved, import/default */
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard, aliasesInfo, widget, widgetInfo) {
+export default function AddWidgetController($scope, widgetService, entityService, $mdDialog, $q, $document, types, dashboard,
+ aliasController, widget, widgetInfo) {
var vm = this;
vm.dashboard = dashboard;
- vm.aliasesInfo = aliasesInfo;
+ vm.aliasController = aliasController;
vm.widget = widget;
vm.widgetInfo = widgetInfo;
@@ -85,7 +86,7 @@ export default function AddWidgetController($scope, widgetService, entityService
}
function cancel () {
- $mdDialog.cancel({aliasesInfo: vm.aliasesInfo});
+ $mdDialog.cancel();
}
function add () {
@@ -94,52 +95,58 @@ export default function AddWidgetController($scope, widgetService, entityService
vm.widget.config = vm.widgetConfig.config;
vm.widget.config.mobileOrder = vm.widgetConfig.layout.mobileOrder;
vm.widget.config.mobileHeight = vm.widgetConfig.layout.mobileHeight;
- $mdDialog.hide({widget: vm.widget, aliasesInfo: vm.aliasesInfo});
+ $mdDialog.hide({widget: vm.widget});
}
}
function fetchEntityKeys (entityAliasId, query, type) {
- var entityAlias = vm.aliasesInfo.entityAliases[entityAliasId];
- if (entityAlias && entityAlias.entityId) {
- return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);
- } else {
- return $q.when([]);
- }
+ var deferred = $q.defer();
+ vm.aliasController.getAliasInfo(entityAliasId).then(
+ function success(aliasInfo) {
+ var entity = aliasInfo.currentEntity;
+ if (entity) {
+ entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
+ function success(keys) {
+ deferred.resolve(keys);
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ } else {
+ deferred.resolve([]);
+ }
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ return deferred.promise;
}
function createEntityAlias (event, alias, allowedEntityTypes) {
var deferred = $q.defer();
- var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null};
+ var singleEntityAlias = {id: null, alias: alias, filter: {}};
$mdDialog.show({
- controller: 'EntityAliasesController',
+ controller: 'EntityAliasDialogController',
controllerAs: 'vm',
- templateUrl: entityAliasesTemplate,
+ templateUrl: entityAliasDialogTemplate,
locals: {
- config: {
- entityAliases: angular.copy(vm.dashboard.configuration.entityAliases),
- widgets: null,
- isSingleEntityAlias: true,
- singleEntityAlias: singleEntityAlias,
- allowedEntityTypes: allowedEntityTypes
- }
+ isAdd: true,
+ allowedEntityTypes: allowedEntityTypes,
+ entityAliases: vm.dashboard.configuration.entityAliases,
+ alias: singleEntityAlias
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: event
}).then(function (singleEntityAlias) {
- vm.dashboard.configuration.entityAliases[singleEntityAlias.id] =
- { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };
- entityService.processEntityAliases(vm.dashboard.configuration.entityAliases).then(
- function(resolution) {
- if (!resolution.error) {
- vm.aliasesInfo = resolution.aliasesInfo;
- }
- deferred.resolve(singleEntityAlias);
- }
- );
+ vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
+ vm.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
+ deferred.resolve(singleEntityAlias);
}, function () {
deferred.reject();
});
diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html
index 870e203..7617760 100644
--- a/ui/src/app/dashboard/add-widget.tpl.html
+++ b/ui/src/app/dashboard/add-widget.tpl.html
@@ -37,7 +37,7 @@
ng-model="vm.widgetConfig"
widget-settings-schema="vm.settingsSchema"
datakey-settings-schema="vm.dataKeySettingsSchema"
- entity-aliases="vm.aliasesInfo.entityAliases"
+ alias-controller="vm.aliasController"
functions-only="vm.functionsOnly"
fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
ui/src/app/dashboard/dashboard.controller.js 41(+20 -21)
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index ac14f79..65eb833 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -15,7 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
import dashboardSettingsTemplate from './dashboard-settings.tpl.html';
import manageDashboardLayoutsTemplate from './layouts/manage-dashboard-layouts.tpl.html';
import manageDashboardStatesTemplate from './states/manage-dashboard-states.tpl.html';
@@ -24,8 +24,10 @@ import selectTargetLayoutTemplate from './layouts/select-target-layout.tpl.html'
/* eslint-enable import/no-unresolved, import/default */
+import AliasController from '../api/alias-controller';
+
/*@ngInject*/
-export default function DashboardController(types, dashboardUtils, widgetService, userService,
+export default function DashboardController(types, utils, dashboardUtils, widgetService, userService,
dashboardService, timeService, entityService, itembuffer, importExport, hotkeys, $window, $rootScope,
$scope, $element, $state, $stateParams, $mdDialog, $mdMedia, $timeout, $document, $q, $translate, $filter) {
@@ -342,6 +344,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.dashboardConfiguration = vm.dashboard.configuration;
vm.dashboardCtx.dashboard = vm.dashboard;
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
+ vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
+ types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
var parentScope = $window.parent.angular.element($window.frameElement).scope();
parentScope.$root.$broadcast('widgetEditModeInited');
parentScope.$root.$apply();
@@ -349,7 +353,13 @@ export default function DashboardController(types, dashboardUtils, widgetService
dashboardService.getDashboard($stateParams.dashboardId)
.then(function success(dashboard) {
vm.dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
- entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
+ vm.dashboardConfiguration = vm.dashboard.configuration;
+ vm.dashboardCtx.dashboard = vm.dashboard;
+ vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
+ vm.dashboardCtx.aliasController = new AliasController($scope, $q, $filter, utils,
+ types, entityService, vm.dashboardCtx.stateController, vm.dashboardConfiguration.entityAliases);
+
+ /* entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
.then(
function(resolution) {
if (resolution.error && !isTenantAdmin()) {
@@ -362,7 +372,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
vm.dashboardCtx.dashboardTimewindow = vm.dashboardConfiguration.timewindow;
}
}
- );
+ );*/
}, function fail() {
vm.configurationError = true;
});
@@ -373,6 +383,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
var layoutsData = dashboardUtils.getStateLayoutsData(vm.dashboard, state);
if (layoutsData) {
vm.dashboardCtx.state = state;
+ vm.dashboardCtx.aliasController.dashboardStateChanged();
var layoutVisibilityChanged = false;
for (var l in vm.layouts) {
var layout = vm.layouts[l];
@@ -916,7 +927,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
templateUrl: addWidgetTemplate,
locals: {
dashboard: vm.dashboard,
- aliasesInfo: vm.dashboardCtx.aliasesInfo,
+ aliasController: vm.dashboardCtx.aliasController,
widget: newWidget,
widgetInfo: widgetTypeInfo
},
@@ -930,10 +941,8 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
}).then(function (result) {
var widget = result.widget;
- vm.dashboardCtx.aliasesInfo = result.aliasesInfo;
addWidget(widget);
- }, function (rejection) {
- vm.dashboardCtx.aliasesInfo = rejection.aliasesInfo;
+ }, function () {
});
}
}
@@ -1025,7 +1034,7 @@ export default function DashboardController(types, dashboardUtils, widgetService
notifyDashboardUpdated();
}
- function showAliasesResolutionError(error) {
+/* function showAliasesResolutionError(error) {
var alert = $mdDialog.alert()
.parent(angular.element($document[0].body))
.clickOutsideToClose(true)
@@ -1037,20 +1046,10 @@ export default function DashboardController(types, dashboardUtils, widgetService
alert._options.fullscreen = true;
$mdDialog.show(alert);
- }
+ }*/
function entityAliasesUpdated() {
- var deferred = $q.defer();
- entityService.processEntityAliases(vm.dashboard.configuration.entityAliases)
- .then(
- function(resolution) {
- if (resolution.aliasesInfo) {
- vm.dashboardCtx.aliasesInfo = resolution.aliasesInfo;
- }
- deferred.resolve();
- }
- );
- return deferred.promise;
+ vm.dashboardCtx.aliasController.updateEntityAliases(vm.dashboard.configuration.entityAliases);
}
function notifyDashboardUpdated() {
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 8338612..202268f 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -57,8 +57,7 @@
</tb-timewindow>
<tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
tooltip-direction="bottom"
- ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
- entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
+ alias-controller="vm.dashboardCtx.aliasController">
</tb-aliases-entity-select>
<md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
ng-click="vm.openEntityAliases($event)">
@@ -179,7 +178,7 @@
<form name="vm.widgetForm" ng-if="vm.isEditingWidget">
<tb-edit-widget
dashboard="vm.dashboard"
- aliases-info="vm.dashboardCtx.aliasesInfo"
+ alias-controller="vm.dashboardCtx.aliasController"
widget="vm.editingWidget"
widget-layout="vm.editingWidgetLayout"
the-form="vm.widgetForm">
ui/src/app/dashboard/edit-widget.directive.js 62(+34 -28)
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index 7cba4ee..6532914 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -15,7 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasDialogTemplate from '../entity/alias/entity-alias-dialog.tpl.html';
import editWidgetTemplate from './edit-widget.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -68,47 +68,53 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
});
scope.fetchEntityKeys = function (entityAliasId, query, type) {
- var entityAlias = scope.aliasesInfo.entityAliases[entityAliasId];
- if (entityAlias && entityAlias.entityId) {
- return entityService.getEntityKeys(entityAlias.entityType, entityAlias.entityId, query, type);
- } else {
- return $q.when([]);
- }
+ var deferred = $q.defer();
+ scope.aliasController.getAliasInfo(entityAliasId).then(
+ function success(aliasInfo) {
+ var entity = aliasInfo.currentEntity;
+ if (entity) {
+ entityService.getEntityKeys(entity.entityType, entity.id, query, type).then(
+ function success(keys) {
+ deferred.resolve(keys);
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ } else {
+ deferred.resolve([]);
+ }
+ },
+ function fail() {
+ deferred.resolve([]);
+ }
+ );
+ return deferred.promise;
};
scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
var deferred = $q.defer();
- var singleEntityAlias = {id: null, alias: alias, entityType: types.entityType.device, entityFilter: null};
+ var singleEntityAlias = {id: null, alias: alias, filter: {}};
$mdDialog.show({
- controller: 'EntityAliasesController',
+ controller: 'EntityAliasDialogController',
controllerAs: 'vm',
- templateUrl: entityAliasesTemplate,
+ templateUrl: entityAliasDialogTemplate,
locals: {
- config: {
- entityAliases: angular.copy(scope.dashboard.configuration.entityAliases),
- widgets: null,
- isSingleEntityAlias: true,
- singleEntityAlias: singleEntityAlias,
- allowedEntityTypes: allowedEntityTypes
- }
+ isAdd: true,
+ allowedEntityTypes: allowedEntityTypes,
+ entityAliases: scope.dashboard.configuration.entityAliases,
+ alias: singleEntityAlias
},
parent: angular.element($document[0].body),
fullscreen: true,
skipHide: true,
targetEvent: event
}).then(function (singleEntityAlias) {
- scope.dashboard.configuration.entityAliases[singleEntityAlias.id] =
- { alias: singleEntityAlias.alias, entityType: singleEntityAlias.entityType, entityFilter: singleEntityAlias.entityFilter };
- entityService.processEntityAliases(scope.dashboard.configuration.entityAliases).then(
- function(resolution) {
- if (!resolution.error) {
- scope.aliasesInfo = resolution.aliasesInfo;
- }
- deferred.resolve(singleEntityAlias);
- }
- );
+ scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
+ scope.aliasController.updateEntityAliases(scope.dashboard.configuration.entityAliases);
+ deferred.resolve(singleEntityAlias);
}, function () {
deferred.reject();
});
@@ -124,7 +130,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
link: linker,
scope: {
dashboard: '=',
- aliasesInfo: '=',
+ aliasController: '=',
widget: '=',
widgetLayout: '=',
theForm: '='
diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html
index 279d311..a9ff0b6 100644
--- a/ui/src/app/dashboard/edit-widget.tpl.html
+++ b/ui/src/app/dashboard/edit-widget.tpl.html
@@ -21,7 +21,7 @@
is-data-enabled="isDataEnabled"
widget-settings-schema="settingsSchema"
datakey-settings-schema="dataKeySettingsSchema"
- entity-aliases="aliasesInfo.entityAliases"
+ alias-controller="aliasController"
functions-only="functionsOnly"
fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
diff --git a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
index ea84858..4a4b25e 100644
--- a/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
+++ b/ui/src/app/dashboard/layouts/dashboard-layout.tpl.html
@@ -45,7 +45,7 @@
widget-layouts="vm.layoutCtx.widgetLayouts"
columns="vm.layoutCtx.gridSettings.columns"
margins="vm.layoutCtx.gridSettings.margins"
- aliases-info="vm.dashboardCtx.aliasesInfo"
+ alias-controller="vm.dashboardCtx.aliasController"
state-controller="vm.dashboardCtx.stateController"
dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow"
is-edit="vm.isEdit"
ui/src/app/device/devices.tpl.html 7(+6 -1)
diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html
index 3ff6c1c..1e467be 100644
--- a/ui/src/app/device/devices.tpl.html
+++ b/ui/src/app/device/devices.tpl.html
@@ -49,11 +49,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.device"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.device"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
ui/src/app/device/index.js 2(+0 -2)
diff --git a/ui/src/app/device/index.js b/ui/src/app/device/index.js
index ea42ee8..5a8adaf 100644
--- a/ui/src/app/device/index.js
+++ b/ui/src/app/device/index.js
@@ -15,7 +15,6 @@
*/
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
-import thingsboardEvent from '../event';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiDevice from '../api/device.service';
import thingsboardApiCustomer from '../api/customer.service';
@@ -30,7 +29,6 @@ import DeviceDirective from './device.directive';
export default angular.module('thingsboard.device', [
uiRouter,
thingsboardGrid,
- thingsboardEvent,
thingsboardApiUser,
thingsboardApiDevice,
thingsboardApiCustomer
diff --git a/ui/src/app/entity/alias/aliases-entity-select-panel.controller.js b/ui/src/app/entity/alias/aliases-entity-select-panel.controller.js
new file mode 100644
index 0000000..5365db7
--- /dev/null
+++ b/ui/src/app/entity/alias/aliases-entity-select-panel.controller.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*@ngInject*/
+export default function AliasesEntitySelectPanelController(mdPanelRef, $scope, $filter, types, aliasController, onEntityAliasesUpdate) {
+
+ var vm = this;
+ vm._mdPanelRef = mdPanelRef;
+ vm.aliasController = aliasController;
+ vm.onEntityAliasesUpdate = onEntityAliasesUpdate;
+ vm.entityAliases = {};
+ vm.entityAliasesInfo = {};
+
+ vm.currentAliasEntityChanged = currentAliasEntityChanged;
+
+ var allEntityAliases = vm.aliasController.getEntityAliases();
+ for (var aliasId in allEntityAliases) {
+ var aliasInfo = vm.aliasController.getInstantAliasInfo(aliasId);
+ if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity) {
+ vm.entityAliasesInfo[aliasId] = angular.copy(aliasInfo);
+ vm.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id;
+ }
+ }
+
+ function currentAliasEntityChanged(aliasId, selectedId) {
+ var resolvedEntities = vm.entityAliasesInfo[aliasId].resolvedEntities;
+ var selected = $filter('filter')(resolvedEntities, {id: selectedId});
+ if (selected && selected.length) {
+ vm.aliasController.updateCurrentAliasEntity(aliasId, selected[0]);
+ if (onEntityAliasesUpdate) {
+ onEntityAliasesUpdate();
+ }
+ }
+ }
+
+}
diff --git a/ui/src/app/entity/alias/entity-alias-dialog.controller.js b/ui/src/app/entity/alias/entity-alias-dialog.controller.js
new file mode 100644
index 0000000..a5b221a
--- /dev/null
+++ b/ui/src/app/entity/alias/entity-alias-dialog.controller.js
@@ -0,0 +1,109 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './entity-alias-dialog.scss';
+
+/*@ngInject*/
+export default function EntityAliasDialogController($scope, $mdDialog, $q, $filter, utils, entityService, types, isAdd, allowedEntityTypes, entityAliases, alias) {
+
+ var vm = this;
+
+ vm.types = types;
+ vm.isAdd = isAdd;
+ vm.allowedEntityTypes = allowedEntityTypes;
+ if (angular.isArray(entityAliases)) {
+ vm.entityAliases = entityAliases;
+ } else {
+ vm.entityAliases = [];
+ for (var aliasId in entityAliases) {
+ vm.entityAliases.push(entityAliases[aliasId]);
+ }
+ }
+ if (vm.isAdd && !alias) {
+ vm.alias = {
+ alias: '',
+ filter: {
+ resolveMultiple: false
+ }
+ };
+ } else {
+ vm.alias = alias;
+ }
+
+ vm.cancel = cancel;
+ vm.save = save;
+
+ $scope.$watch('vm.alias.alias', function (newAlias) {
+ if (newAlias) {
+ var valid = true;
+ var result = $filter('filter')(vm.entityAliases, {alias: newAlias}, true);
+ if (result && result.length) {
+ if (vm.isAdd || vm.alias.id != result[0].id) {
+ valid = false;
+ }
+ }
+ $scope.theForm.aliasName.$setValidity('duplicateAliasName', valid);
+ }
+ });
+
+ $scope.$watch('theForm.$pristine', function() {
+ if ($scope.theForm && !$scope.theForm.$pristine) {
+ $scope.theForm.$setValidity('entityFilter', true);
+ }
+ });
+
+ function validate() {
+ var deferred = $q.defer();
+ var validationResult = {
+ entity: null,
+ stateEntity: false
+ }
+ entityService.resolveAliasFilter(vm.alias.filter, null, 1).then(
+ function success(result) {
+ validationResult.stateEntity = result.stateEntity;
+ var entities = result.entities;
+ if (entities.length) {
+ validationResult.entity = entities[0];
+ }
+ deferred.resolve(validationResult);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ validate().then(
+ function success() {
+ if (vm.isAdd) {
+ vm.alias.id = utils.guid();
+ }
+ $mdDialog.hide(vm.alias);
+ },
+ function fail() {
+ $scope.theForm.$setValidity('entityFilter', false);
+ }
+ )
+ }
+
+}
diff --git a/ui/src/app/entity/alias/entity-alias-dialog.tpl.html b/ui/src/app/entity/alias/entity-alias-dialog.tpl.html
new file mode 100644
index 0000000..b78a795
--- /dev/null
+++ b/ui/src/app/entity/alias/entity-alias-dialog.tpl.html
@@ -0,0 +1,73 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-dialog class="tb-entity-alias-dialog" style="width: 600px;" aria-label="{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}">
+ <form name="theForm" ng-submit="vm.save()">
+ <md-toolbar>
+ <div class="md-toolbar-tools">
+ <h2>{{ (vm.isAdd ? 'alias.add' : 'alias.edit') | translate }}</h2>
+ <span flex></span>
+ <md-button class="md-icon-button" ng-click="vm.cancel()">
+ <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+ </md-button>
+ </div>
+ </md-toolbar>
+ <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
+ <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+ <md-dialog-content>
+ <div class="md-dialog-content">
+ <fieldset ng-disabled="loading">
+ <div flex layout="column">
+ <div layout="row">
+ <md-input-container flex class="md-block">
+ <label translate>alias.name</label>
+ <input required name="aliasName"
+ ng-model="vm.alias.alias"
+ aria-label="{{ 'alias.name' | translate }}">
+ <div ng-messages="theForm.aliasName.$error">
+ <div ng-message="required" translate>alias.name-required</div>
+ <div ng-message="duplicateAliasName" translate>alias.duplicate-alias</div>
+ </div>
+ </md-input-container>
+ <section class="tb-resolve-multiple-switch" layout="column" layout-align="start center">
+ <label class="tb-small resolve-multiple-label" translate>alias.resolve-multiple</label>
+ <md-switch class="resolve-multiple-switch" ng-model="vm.alias.filter.resolveMultiple"
+ aria-label="{{ 'alias.resolve-multiple' | translate }}">
+ </md-switch>
+ </section>
+ </div>
+ <tb-entity-filter
+ ng-model="vm.alias.filter"
+ allowed-entity-types="vm.allowedEntityTypes"
+ the-form="theForm">
+ </tb-entity-filter>
+ <div class="tb-error-messages" ng-messages="theForm.$error" role="alert">
+ <div translate ng-message="entityFilter" class="tb-error-message">alias.entity-filter-no-entity-matched</div>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ </md-dialog-content>
+ <md-dialog-actions layout="row">
+ <span flex></span>
+ <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
+ {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
+ </md-button>
+ <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
+ </md-dialog-actions>
+ </form>
+</md-dialog>
ui/src/app/entity/alias/entity-aliases.scss 49(+49 -0)
diff --git a/ui/src/app/entity/alias/entity-aliases.scss b/ui/src/app/entity/alias/entity-aliases.scss
new file mode 100644
index 0000000..9803be1
--- /dev/null
+++ b/ui/src/app/entity/alias/entity-aliases.scss
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-aliases-dialog {
+ .md-dialog-content {
+ padding-bottom: 0px;
+ padding-top: 0px;
+ }
+ .tb-aliases-header {
+ min-height: 40px;
+ padding: 0 34px 0 34px;
+ margin: 5px;
+ .tb-header-label {
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.570588);
+ }
+ }
+ .tb-alias {
+ padding: 0 0 0 10px;
+ margin: 5px;
+ md-input-container {
+ margin: 0px;
+ }
+ .tb-resolve-multiple-switch {
+ padding-left: 10px;
+ .resolve-multiple-switch {
+ margin: 0;
+ }
+ }
+ .md-button {
+ &.md-icon-button {
+ margin: 0px;
+ }
+ }
+ }
+}
diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
index e5822a4..bcf37dd 100644
--- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
+++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
@@ -22,7 +22,8 @@ import selectTargetLayoutTemplate from '../../dashboard/layouts/select-target-la
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
+export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, $q, $document, dashboardUtils,
+ types, itembuffer, dashboardService, entityId, entityType, entityName, widget) {
var vm = this;
@@ -125,13 +126,8 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
targetDeviceAliases: {}
};
aliasesInfo.datasourceAliases[0] = {
- aliasName: entityName,
- entityType: entityType,
- entityFilter: {
- useFilter: false,
- entityNameFilter: '',
- entityList: [entityId]
- }
+ alias: entityName,
+ filter: dashboardUtils.createSingleEntityFilter(entityType, entityId)
};
itembuffer.addWidgetToDashboard(theDashboard, targetState, targetLayout, vm.widget, aliasesInfo, null, 48, null, -1, -1).then(
function(theDashboard) {
diff --git a/ui/src/app/entity/attribute/attribute-table.directive.js b/ui/src/app/entity/attribute/attribute-table.directive.js
index da7697d..62d2081 100644
--- a/ui/src/app/entity/attribute/attribute-table.directive.js
+++ b/ui/src/app/entity/attribute/attribute-table.directive.js
@@ -26,10 +26,12 @@ import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
import EditAttributeValueController from './edit-attribute-value.controller';
+import AliasController from '../../api/alias-controller';
/*@ngInject*/
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
- $document, $translate, $filter, utils, types, dashboardService, attributeService, widgetService) {
+ $mdUtil, $document, $translate, $filter, utils, types, dashboardUtils,
+ dashboardService, entityService, attributeService, widgetService) {
var linker = function (scope, element, attrs) {
@@ -246,15 +248,19 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
}
scope.nextWidget = function() {
- if (scope.widgetsCarousel.index < scope.widgetsList.length-1) {
- scope.widgetsCarousel.index++;
- }
+ $mdUtil.nextTick(function () {
+ if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) {
+ scope.widgetsCarousel.index++;
+ }
+ });
}
scope.prevWidget = function() {
- if (scope.widgetsCarousel.index > 0) {
- scope.widgetsCarousel.index--;
- }
+ $mdUtil.nextTick(function () {
+ if (scope.widgetsCarousel.index > 0) {
+ scope.widgetsCarousel.index--;
+ }
+ });
}
scope.enterWidgetMode = function() {
@@ -281,23 +287,28 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.firstBundle = true;
scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
- scope.aliasesInfo = {
- entityAliases: {
- '1': {alias: scope.entityName, entityType: scope.entityType, entityId: scope.entityId}
- },
- entityAliasesInfo: {
- '1': [
- {name: scope.entityName, entityType: scope.entityType, id: scope.entityId}
- ]
+ var entityAlias = {
+ id: utils.guid(),
+ alias: scope.entityName,
+ filter: dashboardUtils.createSingleEntityFilter(scope.entityType, scope.entityId)
+ };
+ var entitiAliases = {};
+ entitiAliases[entityAlias.id] = entityAlias;
+
+ var stateController = {
+ getStateParams: function() {
+ return {};
}
};
+ scope.aliasController = new AliasController(scope, $q, $filter, utils,
+ types, entityService, stateController, entitiAliases);
var dataKeyType = scope.attributeScope === types.latestTelemetry ?
types.dataKeyType.timeseries : types.dataKeyType.attribute;
var datasource = {
type: types.datasourceType.entity,
- entityAliasId: '1',
+ entityAliasId: entityAlias.id,
dataKeys: []
}
var i = 0;
diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html
index 1afd1bb..2566a08 100644
--- a/ui/src/app/entity/attribute/attribute-table.tpl.html
+++ b/ui/src/app/entity/attribute/attribute-table.tpl.html
@@ -156,7 +156,7 @@
rn-swipe-disabled="true">
<li ng-repeat="widgets in widgetsList">
<tb-dashboard
- aliases-info="aliasesInfo"
+ alias-controller="aliasController"
widgets="widgets"
get-st-diff="getServerTimeDiff()"
columns="20"
ui/src/app/entity/entity-filter.directive.js 232(+63 -169)
diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js
index b1d92c6..b81f369 100644
--- a/ui/src/app/entity/entity-filter.directive.js
+++ b/ui/src/app/entity/entity-filter.directive.js
@@ -23,7 +23,7 @@ import entityFilterTemplate from './entity-filter.tpl.html';
import './entity-filter.scss';
/*@ngInject*/
-export default function EntityFilterDirective($compile, $templateCache, $q, entityService) {
+export default function EntityFilterDirective($compile, $templateCache, $q, $document, $mdDialog, types, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
@@ -31,181 +31,76 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
element.html(template);
scope.ngModelCtrl = ngModelCtrl;
+ scope.types = types;
+ scope.aliasFilterTypes = entityService.getAliasFilterTypesByEntityTypes(scope.allowedEntityTypes);
- scope.fetchEntities = function(searchText, limit) {
- var deferred = $q.defer();
- entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(function success(result) {
- if (result) {
- deferred.resolve(result);
- } else {
- deferred.resolve([]);
- }
- }, function fail() {
- deferred.reject();
- });
- return deferred.promise;
- }
-
- scope.updateValidity = function() {
- if (ngModelCtrl.$viewValue) {
- var value = ngModelCtrl.$viewValue;
- var valid;
- if (value.useFilter) {
- ngModelCtrl.$setValidity('entityList', true);
- if (angular.isDefined(value.entityNameFilter) && value.entityNameFilter.length > 0) {
- ngModelCtrl.$setValidity('entityNameFilter', true);
- valid = angular.isDefined(scope.model.matchingFilterEntity) && scope.model.matchingFilterEntity != null;
- ngModelCtrl.$setValidity('entityNameFilterEntityMatch', valid);
- } else {
- ngModelCtrl.$setValidity('entityNameFilter', false);
- }
- } else {
- ngModelCtrl.$setValidity('entityNameFilter', true);
- ngModelCtrl.$setValidity('entityNameFilterDeviceMatch', true);
- valid = angular.isDefined(value.entityList) && value.entityList.length > 0;
- ngModelCtrl.$setValidity('entityList', valid);
- }
- }
- }
-
- ngModelCtrl.$render = function () {
- destroyWatchers();
- scope.model = {
- useFilter: false,
- entityList: [],
- entityNameFilter: ''
+ scope.$watch('filter.type', function (newType, prevType) {
+ if (newType && newType != prevType) {
+ updateFilter();
}
- if (ngModelCtrl.$viewValue) {
- var value = ngModelCtrl.$viewValue;
- var model = scope.model;
- model.useFilter = value.useFilter === true ? true: false;
- model.entityList = [];
- model.entityNameFilter = value.entityNameFilter || '';
- processEntityNameFilter(model.entityNameFilter).then(
- function(entity) {
- scope.model.matchingFilterEntity = entity;
- if (value.entityList && value.entityList.length > 0) {
- entityService.getEntities(scope.entityType, value.entityList).then(function (entities) {
- model.entityList = entities;
- updateMatchingEntity();
- initWatchers();
- });
- } else {
- updateMatchingEntity();
- initWatchers();
- }
+ });
+
+ function updateFilter() {
+ var filter = {};
+ filter.type = scope.filter.type;
+ filter.resolveMultiple = scope.filter.resolveMultiple;
+ switch (filter.type) {
+ case types.aliasFilterType.entityList.value:
+ filter.entityType = null;
+ filter.entityList = [];
+ break;
+ case types.aliasFilterType.entityName.value:
+ filter.entityType = null;
+ filter.entityNameFilter = '';
+ break;
+ case types.aliasFilterType.stateEntity.value:
+ break;
+ case types.aliasFilterType.assetType.value:
+ filter.assetType = null;
+ filter.assetNameFilter = '';
+ break;
+ case types.aliasFilterType.deviceType.value:
+ filter.deviceType = null;
+ filter.deviceNameFilter = '';
+ break;
+ case types.aliasFilterType.relationsQuery.value:
+ case types.aliasFilterType.assetSearchQuery.value:
+ case types.aliasFilterType.deviceSearchQuery.value:
+ filter.rootStateEntity = false;
+ filter.rootEntity = null;
+ filter.direction = types.entitySearchDirection.from;
+ filter.maxLevel = 1;
+ if (filter.type === types.aliasFilterType.relationsQuery.value) {
+ filter.filters = [];
+ } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) {
+ filter.relationType = null;
+ filter.assetTypes = [];
+ } else if (filter.type === types.aliasFilterType.deviceSearchQuery.value) {
+ filter.relationType = null;
+ filter.deviceTypes = [];
}
- )
+ break;
}
+ scope.filter = filter;
}
- function updateMatchingEntity() {
- if (scope.model.useFilter) {
- scope.model.matchingEntity = scope.model.matchingFilterEntity;
- } else {
- if (scope.model.entityList && scope.model.entityList.length > 0) {
- scope.model.matchingEntity = scope.model.entityList[0];
- } else {
- scope.model.matchingEntity = null;
- }
- }
- }
-
- function processEntityNameFilter(entityNameFilter) {
- var deferred = $q.defer();
- if (angular.isDefined(entityNameFilter) && entityNameFilter.length > 0) {
- scope.fetchEntities(entityNameFilter, 1).then(function (entities) {
- if (entities && entities.length > 0) {
- deferred.resolve(entities[0]);
- } else {
- deferred.resolve(null);
- }
- });
- } else {
- deferred.resolve(null);
- }
- return deferred.promise;
- }
+ scope.$watch('filter', function () {
+ scope.updateView();
+ });
- function destroyWatchers() {
- if (scope.entityTypeDeregistration) {
- scope.entityTypeDeregistration();
- scope.entityTypeDeregistration = null;
- }
- if (scope.entityListDeregistration) {
- scope.entityListDeregistration();
- scope.entityListDeregistration = null;
- }
- if (scope.useFilterDeregistration) {
- scope.useFilterDeregistration();
- scope.useFilterDeregistration = null;
- }
- if (scope.entityNameFilterDeregistration) {
- scope.entityNameFilterDeregistration();
- scope.entityNameFilterDeregistration = null;
- }
- if (scope.matchingEntityDeregistration) {
- scope.matchingEntityDeregistration();
- scope.matchingEntityDeregistration = null;
- }
+ scope.updateView = function() {
+ ngModelCtrl.$setViewValue(scope.filter);
}
- function initWatchers() {
-
- scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) {
- if (!angular.equals(newEntityType, prevEntityType)) {
- scope.model.entityList = [];
- scope.model.entityNameFilter = '';
- }
- });
-
- scope.entityListDeregistration = scope.$watch('model.entityList', function () {
- if (ngModelCtrl.$viewValue) {
- var value = ngModelCtrl.$viewValue;
- value.entityList = [];
- if (scope.model.entityList && scope.model.entityList.length > 0) {
- for (var i=0;i<scope.model.entityList.length;i++) {
- value.entityList.push(scope.model.entityList[i].id.id);
- }
- }
- updateMatchingEntity();
- ngModelCtrl.$setViewValue(value);
- scope.updateValidity();
- }
- }, true);
- scope.useFilterDeregistration = scope.$watch('model.useFilter', function () {
- if (ngModelCtrl.$viewValue) {
- var value = ngModelCtrl.$viewValue;
- value.useFilter = scope.model.useFilter;
- updateMatchingEntity();
- ngModelCtrl.$setViewValue(value);
- scope.updateValidity();
- }
- });
- scope.entityNameFilterDeregistration = scope.$watch('model.entityNameFilter', function (newNameFilter, prevNameFilter) {
- if (ngModelCtrl.$viewValue) {
- if (!angular.equals(newNameFilter, prevNameFilter)) {
- var value = ngModelCtrl.$viewValue;
- value.entityNameFilter = scope.model.entityNameFilter;
- processEntityNameFilter(value.entityNameFilter).then(
- function(entity) {
- scope.model.matchingFilterEntity = entity;
- updateMatchingEntity();
- ngModelCtrl.$setViewValue(value);
- scope.updateValidity();
- }
- );
- }
- }
- });
-
- scope.matchingEntityDeregistration = scope.$watch('model.matchingEntity', function (newMatchingEntity, prevMatchingEntity) {
- if (!angular.equals(newMatchingEntity, prevMatchingEntity)) {
- if (scope.onMatchingEntityChange) {
- scope.onMatchingEntityChange({entity: newMatchingEntity});
- }
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ scope.filter = ngModelCtrl.$viewValue;
+ } else {
+ scope.filter = {
+ type: null,
+ resolveMultiple: false
}
- });
+ }
}
$compile(element.contents())(scope);
@@ -217,9 +112,8 @@ export default function EntityFilterDirective($compile, $templateCache, $q, enti
require: "^ngModel",
link: linker,
scope: {
- entityType: '=',
- isEdit: '=',
- onMatchingEntityChange: '&'
+ theForm: '=',
+ allowedEntityTypes: '=?'
}
};
ui/src/app/entity/entity-filter.scss 31(+11 -20)
diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss
index 1525842..7e998ca 100644
--- a/ui/src/app/entity/entity-filter.scss
+++ b/ui/src/app/entity/entity-filter.scss
@@ -13,33 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
.tb-entity-filter {
- #entity_list_chips {
- .md-chips {
- padding-bottom: 1px;
+
+ #relationsQueryFilter {
+ padding-top: 20px;
+ tb-entity-select {
+ min-height: 92px;
}
}
- .entity-name-filter-input {
- margin-top: 10px;
- margin-bottom: 0px;
- .md-errors-spacer {
- min-height: 0px;
- }
- }
- .tb-filter-switch {
+
+ .tb-root-state-entity-switch {
padding-left: 10px;
- .filter-switch {
+ .root-state-entity-switch {
margin: 0;
}
- .filter-label {
+ .root-state-entity-label {
margin: 5px 0;
}
}
- .tb-error-messages {
- margin-top: -11px;
- height: 35px;
- .tb-error-message {
- padding-left: 1px;
- }
- }
+
}
\ No newline at end of file
ui/src/app/entity/entity-filter.tpl.html 262(+214 -48)
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index 7e49ba9..4f5af00 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -15,53 +15,219 @@
limitations under the License.
-->
-<section layout='column' class="tb-entity-filter">
- <section layout='row'>
- <section layout="column" flex ng-show="!model.useFilter">
- <md-chips flex
- id="entity_list_chips"
- ng-required="!useFilter"
- ng-model="model.entityList" md-autocomplete-snap
- md-require-match="true">
- <md-autocomplete
- md-no-cache="true"
- id="entity"
- md-selected-item="selectedEntity"
- md-search-text="entitySearchText"
- md-items="item in fetchEntities(entitySearchText, 10)"
- md-item-text="item.name"
- md-min-length="0"
- placeholder="{{ 'entity.entity-list' | translate }}">
- <md-item-template>
- <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
- </md-item-template>
- <md-not-found>
- <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
- </md-not-found>
- </md-autocomplete>
- <md-chip-template>
- <span>
- <strong>{{$chip.name}}</strong>
- </span>
- </md-chip-template>
- </md-chips>
- </section>
- <section layout="row" flex ng-show="model.useFilter">
- <md-input-container flex class="entity-name-filter-input">
- <label translate>entity.name-starts-with</label>
- <input ng-model="model.entityNameFilter" aria-label="{{ 'entity.name-starts-with' | translate }}">
+<div layout='column' class="tb-entity-filter">
+ <md-input-container class="md-block">
+ <label>{{ 'alias.filter-type' | translate }}</label>
+ <md-select required name="filterType"
+ ng-model="filter.type" aria-label="{{ 'alias.filter-type' | translate }}">
+ <md-option ng-repeat="type in aliasFilterTypes" ng-value="type.value">
+ {{type.name | translate}}
+ </md-option>
+ </md-select>
+ <div ng-messages="theForm.filterType.$error">
+ <div ng-message="required" translate>alias.filter-type-required</div>
+ </div>
+ </md-input-container>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.entityList.value" id="entityListFilter">
+ <tb-entity-type-select
+ ng-model="filter.entityType"
+ the-form="theForm"
+ tb-required="true"
+ allowed-entity-types="allowedEntityTypes">
+ </tb-entity-type-select>
+ <tb-entity-list
+ ng-model="filter.entityList"
+ tb-required="true"
+ entity-type="filter.entityType">
+ </tb-entity-list>
+ </section>
+ <section flex layout="column" ng-if="filter.type == types.aliasFilterType.entityName.value" id="entityNameFilter">
+ <tb-entity-type-select
+ ng-model="filter.entityType"
+ the-form="theForm"
+ tb-required="true"
+ allowed-entity-types="allowedEntityTypes">
+ </tb-entity-type-select>
+ <md-input-container class="md-block">
+ <label translate>entity.name-starts-with</label>
+ <input required name="entityNameFilter"
+ ng-model="filter.entityNameFilter"
+ aria-label="{{ 'entity.name-starts-with' | translate }}">
+ <div ng-messages="theForm.entityNameFilter.$error">
+ <div ng-message="required" translate>entity.entity-name-filter-required</div>
+ </div>
+ </md-input-container>
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.stateEntity.value" id="stateEntityFilter">
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.assetType.value" id="assetTypeFilter">
+ <tb-entity-subtype-autocomplete
+ tb-required="true"
+ the-form="theForm"
+ ng-model="filter.assetType"
+ entity-type="types.entityType.asset">
+ </tb-entity-subtype-autocomplete>
+ <md-input-container class="md-block">
+ <label translate>asset.name-starts-with</label>
+ <input name="assetNameFilter"
+ ng-model="filter.assetNameFilter"
+ aria-label="{{ 'asset.name-starts-with' | translate }}">
+ </md-input-container>
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceType.value" id="deviceTypeFilter">
+ <tb-entity-subtype-autocomplete
+ tb-required="true"
+ the-form="theForm"
+ ng-model="filter.deviceType"
+ entity-type="types.entityType.device">
+ </tb-entity-subtype-autocomplete>
+ <md-input-container class="md-block">
+ <label translate>device.name-starts-with</label>
+ <input name="deviceNameFilter"
+ ng-model="filter.deviceNameFilter"
+ aria-label="{{ 'device.name-starts-with' | translate }}">
+ </md-input-container>
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.relationsQuery.value" id="relationsQueryFilter">
+ <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+ <div flex layout="row">
+ <tb-entity-select flex
+ the-form="theForm"
+ tb-required="!filter.rootStateEntity"
+ ng-disabled="filter.rootStateEntity"
+ ng-model="filter.rootEntity">
+ </tb-entity-select>
+ <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+ <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+ <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+ aria-label="{{ 'alias.root-state-entity' | translate }}">
+ </md-switch>
+ </section>
+ </div>
+ <div flex layout="row">
+ <md-input-container class="md-block" style="min-width: 100px;">
+ <label translate>relation.direction</label>
+ <md-select required ng-model="filter.direction">
+ <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+ {{ ('relation.search-direction.' + direction) | translate}}
+ </md-option>
+ </md-select>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alias.max-relation-level</label>
+ <input name="maxRelationLevel"
+ type="number"
+ min="1"
+ step="1"
+ placeholder="{{ 'alias.unlimited-level' | translate }}"
+ ng-model="filter.maxLevel"
+ aria-label="{{ 'alias.max-relation-level' | translate }}">
+ </md-input-container>
+ </div>
+ <div class="md-caption" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>relation.relation-filters</div>
+ <tb-relation-filters
+ ng-model="filter.filters"
+ allowed-entity-types="allowedEntityTypes">
+ </tb-relation-filters>
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.assetSearchQuery.value" id="assetSearchQueryFilter">
+ <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+ <div flex layout="row">
+ <tb-entity-select flex
+ the-form="theForm"
+ tb-required="!filter.rootStateEntity"
+ ng-disabled="filter.rootStateEntity"
+ ng-model="filter.rootEntity">
+ </tb-entity-select>
+ <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+ <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+ <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+ aria-label="{{ 'alias.root-state-entity' | translate }}">
+ </md-switch>
+ </section>
+ </div>
+ <div flex layout="row">
+ <md-input-container class="md-block" style="min-width: 100px;">
+ <label translate>relation.direction</label>
+ <md-select required ng-model="filter.direction">
+ <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+ {{ ('relation.search-direction.' + direction) | translate}}
+ </md-option>
+ </md-select>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alias.max-relation-level</label>
+ <input name="maxRelationLevel"
+ type="number"
+ min="1"
+ step="1"
+ placeholder="{{ 'alias.unlimited-level' | translate }}"
+ ng-model="filter.maxLevel"
+ aria-label="{{ 'alias.max-relation-level' | translate }}">
+ </md-input-container>
+ </div>
+ <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
+ <tb-relation-type-autocomplete flex
+ hide-label
+ the-form="theForm"
+ ng-model="filter.relationType"
+ tb-required="false">
+ </tb-relation-type-autocomplete>
+ <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>asset.asset-types</div>
+ <tb-entity-subtype-list
+ tb-required="true"
+ entity-type="types.entityType.asset"
+ ng-model="filter.assetTypes">
+ </tb-entity-subtype-list>
+ </section>
+ <section layout="column" ng-if="filter.type == types.aliasFilterType.deviceSearchQuery.value" id="deviceSearchQueryFilter">
+ <label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
+ <div flex layout="row">
+ <tb-entity-select flex
+ the-form="theForm"
+ tb-required="!filter.rootStateEntity"
+ ng-disabled="filter.rootStateEntity"
+ ng-model="filter.rootEntity">
+ </tb-entity-select>
+ <section class="tb-root-state-entity-switch" layout="column" layout-align="start center">
+ <label class="tb-small root-state-entity-label" translate>alias.root-state-entity</label>
+ <md-switch class="root-state-entity-switch" ng-model="filter.rootStateEntity"
+ aria-label="{{ 'alias.root-state-entity' | translate }}">
+ </md-switch>
+ </section>
+ </div>
+ <div flex layout="row">
+ <md-input-container class="md-block" style="min-width: 100px;">
+ <label translate>relation.direction</label>
+ <md-select required ng-model="filter.direction">
+ <md-option ng-repeat="direction in types.entitySearchDirection" ng-value="direction">
+ {{ ('relation.search-direction.' + direction) | translate}}
+ </md-option>
+ </md-select>
+ </md-input-container>
+ <md-input-container flex class="md-block">
+ <label translate>alias.max-relation-level</label>
+ <input name="maxRelationLevel"
+ type="number"
+ min="1"
+ step="1"
+ placeholder="{{ 'alias.unlimited-level' | translate }}"
+ ng-model="filter.maxLevel"
+ aria-label="{{ 'alias.max-relation-level' | translate }}">
</md-input-container>
- </section>
- <section class="tb-filter-switch" layout="column" layout-align="center center">
- <label class="tb-small filter-label" translate>entity.use-entity-name-filter</label>
- <md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher">
- </md-switch>
- </section>
+ </div>
+ <div class="md-caption" style="color: rgba(0,0,0,0.57);" translate>relation.relation-type</div>
+ <tb-relation-type-autocomplete flex
+ hide-label
+ the-form="theForm"
+ ng-model="filter.relationType"
+ tb-required="false">
+ </tb-relation-type-autocomplete>
+ <div class="md-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>device.device-types</div>
+ <tb-entity-subtype-list
+ tb-required="true"
+ entity-type="types.entityType.device"
+ ng-model="filter.deviceTypes">
+ </tb-entity-subtype-list>
</section>
- <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
- <div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div>
- <div translate ng-message="entityNameFilter" class="tb-error-message">entity.entity-name-filter-required</div>
- <div translate translate-values='{ entity: model.entityNameFilter }' ng-message="entityNameFilterEntityMatch"
- class="tb-error-message">entity.entity-name-filter-no-entity-matched</div>
- </div>
-</section>
\ No newline at end of file
+</div>
ui/src/app/entity/entity-filter-view.directive.js 205(+205 -0)
diff --git a/ui/src/app/entity/entity-filter-view.directive.js b/ui/src/app/entity/entity-filter-view.directive.js
new file mode 100644
index 0000000..66c1b66
--- /dev/null
+++ b/ui/src/app/entity/entity-filter-view.directive.js
@@ -0,0 +1,205 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entityFilterViewTemplate from './entity-filter-view.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-filter-view.scss';
+
+/*@ngInject*/
+export default function EntityFilterViewDirective($compile, $templateCache, $q, $document, $mdDialog, $translate, types/*, entityService*/) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ var template = $templateCache.get(entityFilterViewTemplate);
+ element.html(template);
+
+ scope.ngModelCtrl = ngModelCtrl;
+ scope.types = types;
+ scope.filterDisplayValue = '';
+
+ scope.$watch('filter', function () {
+ scope.updateDisplayValue();
+ });
+
+ scope.updateDisplayValue = function() {
+ if (scope.filter && scope.filter.type) {
+ var entityType;
+ var prefix;
+ switch (scope.filter.type) {
+ case types.aliasFilterType.entityList.value:
+ entityType = scope.filter.entityType;
+ var count = scope.filter.entityList.length;
+ scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].list, {count: count}, 'messageformat');
+ break;
+ case types.aliasFilterType.entityName.value:
+ entityType = scope.filter.entityType;
+ prefix = scope.filter.entityNameFilter;
+ scope.filterDisplayValue = $translate.instant(types.entityTypeTranslations[entityType].nameStartsWith, {prefix: prefix});
+ break;
+ case types.aliasFilterType.stateEntity.value:
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-state-entity-description');
+ break;
+ case types.aliasFilterType.assetType.value:
+ var assetType = scope.filter.assetType;
+ prefix = scope.filter.assetNameFilter;
+ if (prefix && prefix.length) {
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-and-name-description', {assetType: assetType, prefix: prefix});
+ } else {
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-type-description', {assetType: assetType});
+ }
+ break;
+ case types.aliasFilterType.deviceType.value:
+ var deviceType = scope.filter.deviceType;
+ prefix = scope.filter.deviceNameFilter;
+ if (prefix && prefix.length) {
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-and-name-description', {deviceType: deviceType, prefix: prefix});
+ } else {
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-device-type-description', {deviceType: deviceType});
+ }
+ break;
+ case types.aliasFilterType.relationsQuery.value:
+ var rootEntityText;
+ var directionText;
+ var allEntitiesText = $translate.instant('alias.all-entities');
+ var anyRelationText = $translate.instant('alias.any-relation');
+ if (scope.filter.rootStateEntity) {
+ rootEntityText = $translate.instant('alias.state-entity');
+ } else {
+ rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
+ }
+ directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
+ var relationFilters = scope.filter.filters;
+ if (relationFilters && relationFilters.length) {
+ var relationFiltersDisplayValues = [];
+ relationFilters.forEach(function(relationFilter) {
+ var entitiesText;
+ if (relationFilter.entityTypes && relationFilter.entityTypes.length) {
+ var entitiesNamesList = [];
+ relationFilter.entityTypes.forEach(function(entityType) {
+ entitiesNamesList.push(
+ $translate.instant(types.entityTypeTranslations[entityType].typePlural)
+ );
+ });
+ entitiesText = entitiesNamesList.join(', ');
+ } else {
+ entitiesText = allEntitiesText;
+ }
+ var relationTypeText;
+ if (relationFilter.relationType && relationFilter.relationType.length) {
+ relationTypeText = "'" + relationFilter.relationType + "'";
+ } else {
+ relationTypeText = anyRelationText;
+ }
+ var relationFilterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
+ {
+ entities: entitiesText,
+ relationType: relationTypeText,
+ direction: directionText,
+ rootEntity: rootEntityText
+ }
+ );
+ relationFiltersDisplayValues.push(relationFilterDisplayValue);
+ });
+ scope.filterDisplayValue = relationFiltersDisplayValues.join(', ');
+ } else {
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-relations-query-description',
+ {
+ entities: allEntitiesText,
+ relationType: anyRelationText,
+ direction: directionText,
+ rootEntity: rootEntityText
+ }
+ );
+ }
+ break;
+ case types.aliasFilterType.assetSearchQuery.value:
+ case types.aliasFilterType.deviceSearchQuery.value:
+ allEntitiesText = $translate.instant('alias.all-entities');
+ anyRelationText = $translate.instant('alias.any-relation');
+ if (scope.filter.rootStateEntity) {
+ rootEntityText = $translate.instant('alias.state-entity');
+ } else {
+ rootEntityText = $translate.instant(types.entityTypeTranslations[scope.filter.rootEntity.entityType].type);
+ }
+ directionText = $translate.instant('relation.direction-type.' + scope.filter.direction);
+ var relationTypeText;
+ if (scope.filter.relationType && scope.filter.relationType.length) {
+ relationTypeText = "'" + scope.filter.relationType + "'";
+ } else {
+ relationTypeText = anyRelationText;
+ }
+
+ var translationValues = {
+ relationType: relationTypeText,
+ direction: directionText,
+ rootEntity: rootEntityText
+ }
+
+ if (scope.filter.type == types.aliasFilterType.assetSearchQuery.value) {
+ var assetTypesQuoted = [];
+ scope.filter.assetTypes.forEach(function(assetType) {
+ assetTypesQuoted.push("'"+assetType+"'");
+ });
+ var assetTypesText = assetTypesQuoted.join(', ');
+ translationValues.assetTypes = assetTypesText;
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-asset-search-query-description',
+ translationValues
+ );
+ } else {
+ var deviceTypesQuoted = [];
+ scope.filter.deviceTypes.forEach(function(deviceType) {
+ deviceTypesQuoted.push("'"+deviceType+"'");
+ });
+ var deviceTypesText = deviceTypesQuoted.join(', ');
+ translationValues.deviceTypes = deviceTypesText;
+ scope.filterDisplayValue = $translate.instant('alias.filter-type-device-search-query-description',
+ translationValues
+ );
+ }
+ break;
+ default:
+ scope.filterDisplayValue = scope.filter.type;
+ break;
+ }
+ } else {
+ scope.filterDisplayValue = '';
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ scope.filter = ngModelCtrl.$viewValue;
+ } else {
+ scope.filter = null;
+ }
+ }
+
+ $compile(element.contents())(scope);
+
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: true
+ };
+
+}
ui/src/app/entity/entity-filter-view.scss 33(+33 -0)
diff --git a/ui/src/app/entity/entity-filter-view.scss b/ui/src/app/entity/entity-filter-view.scss
new file mode 100644
index 0000000..437f296
--- /dev/null
+++ b/ui/src/app/entity/entity-filter-view.scss
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-entity-filter-view {
+ .entity-filter-empty {
+ color: rgba(221, 44, 0, 0.87);
+ font-size: 14px;
+ line-height: 16px;
+ }
+ .entity-filter-type {
+ font-size: 14px;
+ line-height: 16px;
+ color: rgba(0, 0, 0, 0.570588);
+ }
+ .entity-filter-value {
+ font-size: 14px;
+ line-height: 16px;
+ color: rgba(0, 0, 0, 0.570588);
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/entity/entity-filter-view.tpl.html b/ui/src/app/entity/entity-filter-view.tpl.html
new file mode 100644
index 0000000..84c5405
--- /dev/null
+++ b/ui/src/app/entity/entity-filter-view.tpl.html
@@ -0,0 +1,24 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<div layout='column' class="tb-entity-filter-view">
+ <div ng-if="!filter || !filter.type" class="entity-filter-empty" translate>alias.no-entity-filter-specified</div>
+ <div ng-if="filter && filter.type" layout="column">
+ <div class="entity-filter-value">{{ filterDisplayValue }}</div>
+ </div>
+</div>
ui/src/app/entity/entity-list.directive.js 130(+130 -0)
diff --git a/ui/src/app/entity/entity-list.directive.js b/ui/src/app/entity/entity-list.directive.js
new file mode 100644
index 0000000..0863153
--- /dev/null
+++ b/ui/src/app/entity/entity-list.directive.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entityListTemplate from './entity-list.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-list.scss';
+
+/*@ngInject*/
+export default function EntityListDirective($compile, $templateCache, $q, $mdUtil, entityService) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ var template = $templateCache.get(entityListTemplate);
+ element.html(template);
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ scope.$watch('tbRequired', function () {
+ scope.updateValidity();
+ });
+
+ scope.fetchEntities = function(searchText, limit) {
+ var deferred = $q.defer();
+ entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit).then(
+ function success(result) {
+ if (result) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([]);
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ scope.updateValidity = function() {
+ var value = ngModelCtrl.$viewValue;
+ var valid = !scope.tbRequired || value && value.length > 0;
+ ngModelCtrl.$setValidity('entityList', valid);
+ }
+
+ ngModelCtrl.$render = function () {
+ destroyWatchers();
+ var value = ngModelCtrl.$viewValue;
+ scope.entityList = [];
+ if (value && value.length > 0) {
+ entityService.getEntities(scope.entityType, value).then(function (entities) {
+ scope.entityList = entities;
+ initWatchers();
+ });
+ } else {
+ initWatchers();
+ }
+ }
+
+ function initWatchers() {
+ scope.entityTypeDeregistration = scope.$watch('entityType', function (newEntityType, prevEntityType) {
+ if (!angular.equals(newEntityType, prevEntityType)) {
+ scope.entityList = [];
+ }
+ });
+ scope.entityListDeregistration = scope.$watch('entityList', function () {
+ var ids = [];
+ if (scope.entityList && scope.entityList.length > 0) {
+ for (var i=0;i<scope.entityList.length;i++) {
+ ids.push(scope.entityList[i].id.id);
+ }
+ }
+ var value = ngModelCtrl.$viewValue;
+ if (!angular.equals(ids, value)) {
+ ngModelCtrl.$setViewValue(ids);
+ }
+ scope.updateValidity();
+ }, true);
+ }
+
+ function destroyWatchers() {
+ if (scope.entityTypeDeregistration) {
+ scope.entityTypeDeregistration();
+ scope.entityTypeDeregistration = null;
+ }
+ if (scope.entityListDeregistration) {
+ scope.entityListDeregistration();
+ scope.entityListDeregistration = null;
+ }
+ }
+
+ $compile(element.contents())(scope);
+
+ $mdUtil.nextTick(function(){
+ var inputElement = angular.element('input', element);
+ inputElement.on('blur', function() {
+ scope.inputTouched = true;
+ } );
+ });
+
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ disabled:'=ngDisabled',
+ tbRequired: '=?',
+ entityType: '='
+ }
+ };
+
+}
ui/src/app/entity/entity-list.scss 30(+30 -0)
diff --git a/ui/src/app/entity/entity-list.scss b/ui/src/app/entity/entity-list.scss
new file mode 100644
index 0000000..437e292
--- /dev/null
+++ b/ui/src/app/entity/entity-list.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*.tb-entity-list {
+ #entity_list_chips {
+ .md-chips {
+ padding-bottom: 1px;
+ }
+ }
+ .tb-error-messages {
+ margin-top: -11px;
+ height: 35px;
+ .tb-error-message {
+ padding-left: 1px;
+ }
+ }
+}*/
\ No newline at end of file
ui/src/app/entity/entity-list.tpl.html 52(+52 -0)
diff --git a/ui/src/app/entity/entity-list.tpl.html b/ui/src/app/entity/entity-list.tpl.html
new file mode 100644
index 0000000..6bbb920
--- /dev/null
+++ b/ui/src/app/entity/entity-list.tpl.html
@@ -0,0 +1,52 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<section flex layout='column' class="tb-entity-list">
+ <md-chips flex
+ readonly="disabled"
+ id="entity_list_chips"
+ ng-required="tbRequired"
+ ng-model="entityList"
+ md-autocomplete-snap
+ md-require-match="true">
+ <md-autocomplete
+ md-no-cache="true"
+ id="entity"
+ md-selected-item="selectedEntity"
+ md-search-text="entitySearchText"
+ md-items="item in fetchEntities(entitySearchText, 10)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{ 'entity.entity-list' | translate }}">
+ <md-item-template>
+ <span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span>
+ </md-item-template>
+ <md-not-found>
+ <span translate translate-values='{ entity: entitySearchText }'>entity.no-entities-matching</span>
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <span>
+ <strong>{{$chip.name}}</strong>
+ </span>
+ </md-chip-template>
+ </md-chips>
+ <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched" role="alert">
+ <div translate ng-message="entityList" class="tb-error-message">entity.entity-list-empty</div>
+ </div>
+</section>
\ No newline at end of file
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
index 98110b0..76c15d3 100644
--- a/ui/src/app/entity/entity-subtype-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -114,6 +114,9 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
scope.selectEntitySubtypeText = 'asset.select-asset-type';
scope.entitySubtypeText = 'asset.asset-type';
scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ scope.$on('assetSaved', function() {
+ scope.entitySubtypes = null;
+ });
} else if (scope.entityType == types.entityType.device) {
scope.selectEntitySubtypeText = 'device.select-device-type';
scope.entitySubtypeText = 'device.device-type';
ui/src/app/entity/entity-subtype-list.directive.js 146(+146 -0)
diff --git a/ui/src/app/entity/entity-subtype-list.directive.js b/ui/src/app/entity/entity-subtype-list.directive.js
new file mode 100644
index 0000000..c7d6329
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.directive.js
@@ -0,0 +1,146 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-subtype-list.scss';
+
+/*@ngInject*/
+export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ var template = $templateCache.get(entitySubtypeListTemplate);
+ element.html(template);
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+
+ scope.entitySubtypesList = [];
+ scope.entitySubtypes = null;
+
+ if (scope.entityType == types.entityType.asset) {
+ scope.placeholder = scope.tbRequired ? $translate.instant('asset.enter-asset-type')
+ : $translate.instant('asset.any-asset');
+ scope.secondaryPlaceholder = '+' + $translate.instant('asset.asset-type');
+ scope.noSubtypesMathingText = 'asset.no-asset-types-matching';
+ scope.subtypeListEmptyText = 'asset.asset-type-list-empty';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.placeholder = scope.tbRequired ? $translate.instant('device.enter-device-type')
+ : $translate.instant('device.any-device');
+ scope.secondaryPlaceholder = '+' + $translate.instant('device.device-type');
+ scope.noSubtypesMathingText = 'device.no-device-types-matching';
+ scope.subtypeListEmptyText = 'device.device-type-list-empty';
+ }
+
+ scope.$watch('tbRequired', function () {
+ scope.updateValidity();
+ });
+
+ scope.fetchEntitySubtypes = function(searchText) {
+ var deferred = $q.defer();
+ loadSubTypes().then(
+ function success(subTypes) {
+ var result = $filter('filter')(subTypes, {'$': searchText});
+ if (result && result.length) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([searchText]);
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ scope.updateValidity = function() {
+ var value = ngModelCtrl.$viewValue;
+ var valid = !scope.tbRequired || value && value.length > 0;
+ ngModelCtrl.$setValidity('entitySubtypesList', valid);
+ }
+
+ ngModelCtrl.$render = function () {
+ scope.entitySubtypesList = ngModelCtrl.$viewValue;
+ if (!scope.entitySubtypesList) {
+ scope.entitySubtypesList = [];
+ }
+ }
+
+ scope.$watch('entitySubtypesList', function () {
+ ngModelCtrl.$setViewValue(scope.entitySubtypesList);
+ scope.updateValidity();
+ }, true);
+
+ function loadSubTypes() {
+ var deferred = $q.defer();
+ if (!scope.entitySubtypes) {
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes = [];
+ types.forEach(function (type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ deferred.resolve(scope.entitySubtypes);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.resolve(scope.entitySubtypes);
+ }
+ return deferred.promise;
+ }
+
+ $compile(element.contents())(scope);
+
+ $mdUtil.nextTick(function(){
+ var inputElement = angular.element('input', element);
+ inputElement.on('blur', function() {
+ scope.inputTouched = true;
+ } );
+ });
+
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ disabled:'=ngDisabled',
+ tbRequired: '=?',
+ entityType: "="
+ }
+ };
+
+}
ui/src/app/entity/entity-subtype-list.scss 30(+30 -0)
diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss
new file mode 100644
index 0000000..bbb2a1c
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*.tb-entity-subtype-list {
+ #entity_subtype_list_chips {
+ .md-chips {
+ padding-bottom: 1px;
+ }
+ }
+ .tb-error-messages {
+ margin-top: -11px;
+ height: 35px;
+ .tb-error-message {
+ padding-left: 1px;
+ }
+ }
+}*/
diff --git a/ui/src/app/entity/entity-subtype-list.tpl.html b/ui/src/app/entity/entity-subtype-list.tpl.html
new file mode 100644
index 0000000..2a1519a
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-list.tpl.html
@@ -0,0 +1,54 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<section flex layout='column' class="tb-entity-subtype-list">
+ <md-chips flex
+ readonly="disabled"
+ id="entity_subtype_list_chips"
+ ng-required="tbRequired"
+ ng-model="entitySubtypesList"
+ placeholder="{{placeholder}}"
+ secondary-placeholder="{{secondaryPlaceholder}}"
+ md-autocomplete-snap
+ md-require-match="false">
+ <md-autocomplete
+ md-no-cache="true"
+ id="entitySubtype"
+ md-selected-item="selectedEntitySubtype"
+ md-search-text="entitySubtypeSearchText"
+ md-items="item in fetchEntitySubtypes(entitySubtypeSearchText)"
+ md-item-text="item"
+ md-min-length="0"
+ placeholder="{{ (!entitySubtypesList || !entitySubtypesList.length) ? placeholder : secondaryPlaceholder }}">
+ <md-item-template>
+ <span md-highlight-text="entitySubtypeSearchText" md-highlight-flags="^i">{{item}}</span>
+ </md-item-template>
+ <md-not-found>
+ <span translate translate-values='{ entitySubtype: entitySubtypeSearchText }'>{{noSubtypesMathingText}}</span>
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <span>
+ <strong>{{$chip}}</strong>
+ </span>
+ </md-chip-template>
+ </md-chips>
+ <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
+ <div translate ng-message="entitySubtypesList" class="tb-error-message">{{subtypeListEmptyText}}</div>
+ </div>
+</section>
ui/src/app/entity/entity-type-list.directive.js 111(+111 -0)
diff --git a/ui/src/app/entity/entity-type-list.directive.js b/ui/src/app/entity/entity-type-list.directive.js
new file mode 100644
index 0000000..cb07cfc
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.directive.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entityTypeListTemplate from './entity-type-list.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import './entity-type-list.scss';
+
+/*@ngInject*/
+export default function EntityTypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, entityService) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ var template = $templateCache.get(entityTypeListTemplate);
+ element.html(template);
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ scope.placeholder = scope.tbRequired ? $translate.instant('entity.enter-entity-type')
+ : $translate.instant('entity.any-entity');
+ scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
+
+ var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
+ scope.entityTypesList = [];
+ for (var type in entityTypes) {
+ var entityTypeInfo = {};
+ entityTypeInfo.value = entityTypes[type];
+ entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
+ scope.entityTypesList.push(entityTypeInfo);
+ }
+
+ scope.$watch('tbRequired', function () {
+ scope.updateValidity();
+ });
+
+ scope.fetchEntityTypes = function(searchText) {
+ var deferred = $q.defer();
+ var entityTypes = $filter('filter')(scope.entityTypesList, {name: searchText});
+ deferred.resolve(entityTypes);
+ return deferred.promise;
+ }
+
+ scope.updateValidity = function() {
+ var value = ngModelCtrl.$viewValue;
+ var valid = !scope.tbRequired || value && value.length > 0;
+ ngModelCtrl.$setValidity('entityTypeList', valid);
+ }
+
+ ngModelCtrl.$render = function () {
+ scope.entityTypeList = [];
+ var value = ngModelCtrl.$viewValue;
+ if (value && value.length) {
+ value.forEach(function(type) {
+ var entityTypeInfo = {};
+ entityTypeInfo.value = type;
+ entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
+ scope.entityTypeList.push(entityTypeInfo);
+ });
+ }
+ }
+
+ scope.$watch('entityTypeList', function () {
+ var values = [];
+ if (scope.entityTypeList && scope.entityTypeList.length) {
+ scope.entityTypeList.forEach(function(entityType) {
+ values.push(entityType.value);
+ });
+ }
+ ngModelCtrl.$setViewValue(values);
+ scope.updateValidity();
+ }, true);
+
+ $compile(element.contents())(scope);
+
+ $mdUtil.nextTick(function(){
+ var inputElement = angular.element('input', element);
+ inputElement.on('blur', function() {
+ scope.inputTouched = true;
+ } );
+ });
+
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ disabled:'=ngDisabled',
+ tbRequired: '=?',
+ allowedEntityTypes: '=?'
+ }
+ };
+
+}
ui/src/app/entity/entity-type-list.scss 30(+30 -0)
diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss
new file mode 100644
index 0000000..b94b992
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*.tb-entity-type-list {
+ #entity_type_list_chips {
+ .md-chips {
+ padding-bottom: 1px;
+ }
+ }
+ .tb-error-messages {
+ margin-top: -11px;
+ height: 35px;
+ .tb-error-message {
+ padding-left: 1px;
+ }
+ }
+}*/
\ No newline at end of file
ui/src/app/entity/entity-type-list.tpl.html 54(+54 -0)
diff --git a/ui/src/app/entity/entity-type-list.tpl.html b/ui/src/app/entity/entity-type-list.tpl.html
new file mode 100644
index 0000000..ff10a30
--- /dev/null
+++ b/ui/src/app/entity/entity-type-list.tpl.html
@@ -0,0 +1,54 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<section flex layout='column' class="tb-entity-type-list">
+ <md-chips flex
+ readonly="disabled"
+ id="entity_type_list_chips"
+ ng-required="tbRequired"
+ ng-model="entityTypeList"
+ placeholder="{{placeholder}}"
+ secondary-placeholder="{{secondaryPlaceholder}}"
+ md-autocomplete-snap
+ md-require-match="true">
+ <md-autocomplete
+ md-no-cache="true"
+ id="entityType"
+ md-selected-item="selectedEntityType"
+ md-search-text="entityTypeSearchText"
+ md-items="item in fetchEntityTypes(entityTypeSearchText)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{ (!entityTypeList || !entityTypeList.length) ? placeholder : secondaryPlaceholder }}">
+ <md-item-template>
+ <span md-highlight-text="entityTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
+ </md-item-template>
+ <md-not-found>
+ <span translate translate-values='{ entityType: entityTypeSearchText }'>entity.no-entity-types-matching</span>
+ </md-not-found>
+ </md-autocomplete>
+ <md-chip-template>
+ <span>
+ <strong>{{$chip.name}}</strong>
+ </span>
+ </md-chip-template>
+ </md-chips>
+ <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" ng-if="inputTouched && tbRequired" role="alert">
+ <div translate ng-message="entityTypeList" class="tb-error-message">entity.entity-type-list-empty</div>
+ </div>
+</section>
diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js
index 1dc6741..068c141 100644
--- a/ui/src/app/entity/entity-type-select.directive.js
+++ b/ui/src/app/entity/entity-type-select.directive.js
@@ -23,7 +23,7 @@ import entityTypeSelectTemplate from './entity-type-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function EntityTypeSelect($compile, $templateCache, utils, userService, types) {
+export default function EntityTypeSelect($compile, $templateCache, utils, entityService, userService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entityTypeSelectTemplate);
@@ -39,39 +39,10 @@ export default function EntityTypeSelect($compile, $templateCache, utils, userSe
scope.ngModelCtrl = ngModelCtrl;
- var authority = userService.getAuthority();
- scope.entityTypes = {};
- switch(authority) {
- case 'SYS_ADMIN':
- scope.entityTypes.tenant = types.entityType.tenant;
- scope.entityTypes.rule = types.entityType.rule;
- scope.entityTypes.plugin = types.entityType.plugin;
- break;
- case 'TENANT_ADMIN':
- scope.entityTypes.device = types.entityType.device;
- scope.entityTypes.asset = types.entityType.asset;
- scope.entityTypes.customer = types.entityType.customer;
- scope.entityTypes.rule = types.entityType.rule;
- scope.entityTypes.plugin = types.entityType.plugin;
- scope.entityTypes.dashboard = types.entityType.dashboard;
- break;
- case 'CUSTOMER_USER':
- scope.entityTypes.device = types.entityType.device;
- scope.entityTypes.asset = types.entityType.asset;
- scope.entityTypes.dashboard = types.entityType.dashboard;
- break;
- }
-
- if (scope.allowedEntityTypes) {
- for (var entityType in scope.entityTypes) {
- if (scope.allowedEntityTypes.indexOf(scope.entityTypes[entityType]) === -1) {
- delete scope.entityTypes[entityType];
- }
- }
- }
+ scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
scope.typeName = function(type) {
- return utils.entityTypeName(type);
+ return type ? types.entityTypeTranslations[type].type : '';
}
scope.updateValidity = function () {
ui/src/app/entity/index.js 18(+15 -3)
diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js
index e8cc437..2b8d434 100644
--- a/ui/src/app/entity/index.js
+++ b/ui/src/app/entity/index.js
@@ -14,34 +14,46 @@
* limitations under the License.
*/
-import EntityAliasesController from './entity-aliases.controller';
+import EntityAliasesController from './alias/entity-aliases.controller';
+import EntityAliasDialogController from './alias/entity-alias-dialog.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
+import EntityTypeListDirective from './entity-type-list.directive';
+import EntitySubtypeListDirective from './entity-subtype-list.directive';
import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
import EntityAutocompleteDirective from './entity-autocomplete.directive';
+import EntityListDirective from './entity-list.directive';
import EntitySelectDirective from './entity-select.directive';
import EntityFilterDirective from './entity-filter.directive';
-import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
-import AliasesEntitySelectDirective from './aliases-entity-select.directive';
+import EntityFilterViewDirective from './entity-filter-view.directive';
+import AliasesEntitySelectPanelController from './alias/aliases-entity-select-panel.controller';
+import AliasesEntitySelectDirective from './alias/aliases-entity-select.directive';
import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
import AttributeTableDirective from './attribute/attribute-table.directive';
+import RelationFiltersDirective from './relation/relation-filters.directive';
import RelationTableDirective from './relation/relation-table.directive';
import RelationTypeAutocompleteDirective from './relation/relation-type-autocomplete.directive';
export default angular.module('thingsboard.entity', [])
.controller('EntityAliasesController', EntityAliasesController)
+ .controller('EntityAliasDialogController', EntityAliasDialogController)
.controller('AliasesEntitySelectPanelController', AliasesEntitySelectPanelController)
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
+ .directive('tbEntityTypeList', EntityTypeListDirective)
+ .directive('tbEntitySubtypeList', EntitySubtypeListDirective)
.directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
.directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
.directive('tbEntityAutocomplete', EntityAutocompleteDirective)
+ .directive('tbEntityList', EntityListDirective)
.directive('tbEntitySelect', EntitySelectDirective)
.directive('tbEntityFilter', EntityFilterDirective)
+ .directive('tbEntityFilterView', EntityFilterViewDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
+ .directive('tbRelationFilters', RelationFiltersDirective)
.directive('tbRelationTable', RelationTableDirective)
.directive('tbRelationTypeAutocomplete', RelationTypeAutocompleteDirective)
.name;
diff --git a/ui/src/app/entity/relation/relation-filters.directive.js b/ui/src/app/entity/relation/relation-filters.directive.js
new file mode 100644
index 0000000..59821dd
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.directive.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './relation-filters.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import relationFiltersTemplate from './relation-filters.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RelationFilters($compile, $templateCache) {
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ scope: {
+ allowedEntityTypes: '=?'
+ },
+ link: linker
+ };
+
+ function linker( scope, element, attrs, ngModelCtrl ) {
+
+ var template = $templateCache.get(relationFiltersTemplate);
+ element.html(template);
+
+ scope.relationFilters = [];
+
+ scope.addFilter = addFilter;
+ scope.removeFilter = removeFilter;
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ var value = ngModelCtrl.$viewValue;
+ value.forEach(function (filter) {
+ scope.relationFilters.push(filter);
+ });
+ }
+ scope.$watch('relationFilters', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ updateValue();
+ }
+ }, true);
+ }
+
+ function addFilter() {
+ var filter = {
+ relationType: null,
+ entityTypes: []
+ };
+ scope.relationFilters.push(filter);
+ }
+
+ function removeFilter($event, filter) {
+ var index = scope.relationFilters.indexOf(filter);
+ if (index > -1) {
+ scope.relationFilters.splice(index, 1);
+ }
+ }
+
+ function updateValue() {
+ var value = [];
+ scope.relationFilters.forEach(function (filter) {
+ value.push(filter);
+ });
+ ngModelCtrl.$setViewValue(value);
+ }
+ $compile(element.contents())(scope);
+ }
+}
diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss
new file mode 100644
index 0000000..50d49af
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.scss
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-relation-filters {
+ .header {
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-bottom: 5px;
+ .cell {
+ padding-left: 5px;
+ padding-right: 5px;
+ color: rgba(0,0,0,.54);
+ font-size: 12px;
+ font-weight: 700;
+ white-space: nowrap;
+ }
+ }
+ .body {
+ padding-left: 5px;
+ padding-right: 5px;
+ max-height: 300px;
+ overflow: auto;
+ padding-bottom: 20px;
+ .row {
+ padding-top: 5px;
+ }
+ .cell {
+ padding-left: 5px;
+ padding-right: 5px;
+
+ md-select {
+ margin: 0 0 24px;
+ }
+
+ md-input-container {
+ margin: 0;
+ }
+
+ md-chips-wrap {
+ padding: 0px;
+ margin: 0 0 24px;
+ .md-chip-input-container {
+ margin: 0;
+ }
+ md-autocomplete {
+ height: 30px;
+ md-autocomplete-wrap {
+ height: 30px;
+ }
+ }
+ }
+ .md-chips .md-chip-input-container input {
+ padding: 2px 2px 2px;
+ height: 26px;
+ line-height: 26px;
+ }
+
+ }
+
+ .md-button {
+ margin: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/entity/relation/relation-filters.tpl.html b/ui/src/app/entity/relation/relation-filters.tpl.html
new file mode 100644
index 0000000..679a488
--- /dev/null
+++ b/ui/src/app/entity/relation/relation-filters.tpl.html
@@ -0,0 +1,67 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<div class="tb-relation-filters">
+ <div class="header" ng-show="relationFilters.length">
+ <div layout="row" layout-align="start center">
+ <span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
+ <span class="cell" flex translate>entity.entity-types</span>
+ <span class="cell" style="width: 40px; min-width: 40px;"> </span>
+ </div>
+ </div>
+ <div class="body" ng-show="relationFilters.length">
+ <div class="row" ng-form name="relationFilterForm" flex layout="row" layout-align="start center" ng-repeat="filter in relationFilters track by $index">
+ <div class="md-whiteframe-1dp" flex layout="row" layout-align="start center">
+ <tb-relation-type-autocomplete class="cell" style="width: 200px; min-width: 200px;"
+ hide-label
+ the-form="relationFilterForm"
+ ng-model="filter.relationType"
+ tb-required="false">
+ </tb-relation-type-autocomplete>
+ <tb-entity-type-list class="cell" flex
+ ng-model="filter.entityTypes"
+ allowed-entity-types="allowedEntityTypes"
+ tb-required="false">
+ </tb-entity-type-list>
+ <md-button ng-disabled="loading" class="md-icon-button md-primary" style="width: 40px; min-width: 40px;"
+ ng-click="removeFilter($event, filter)" aria-label="{{ 'action.remove' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'relation.remove-relation-filter' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
+ close
+ </md-icon>
+ </md-button>
+ </div>
+ </div>
+ </div>
+ <div class="any-filter" ng-show="!relationFilters.length">
+ <span layout-align="center center"
+ class="tb-prompt" translate>relation.any-relation</span>
+ </div>
+ <div>
+ <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'relation.add-relation-filter' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
+ add
+ </md-icon>
+ {{ 'action.add' | translate }}
+ </md-button>
+ </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js
index 729ff36..f0c192b 100644
--- a/ui/src/app/entity/relation/relation-table.directive.js
+++ b/ui/src/app/entity/relation/relation-table.directive.js
@@ -218,9 +218,9 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
function success(allRelations) {
allRelations.forEach(function(relation) {
if (vm.direction == vm.types.entitySearchDirection.from) {
- relation.toEntityTypeName = $translate.instant(utils.entityTypeName(relation.to.entityType));
+ relation.toEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.to.entityType].type);
} else {
- relation.fromEntityTypeName = $translate.instant(utils.entityTypeName(relation.from.entityType));
+ relation.fromEntityTypeName = $translate.instant(types.entityTypeTranslations[relation.from.entityType].type);
}
});
vm.allRelations = allRelations;
diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
index a7e5ea4..4b5480d 100644
--- a/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
+++ b/ui/src/app/entity/relation/relation-type-autocomplete.directive.js
@@ -29,6 +29,8 @@ export default function RelationTypeAutocomplete($compile, $templateCache, $q, $
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.hideLabel = angular.isDefined(attrs.hideLabel) ? true : false;
+
scope.relationType = null;
scope.relationTypeSearchText = '';
scope.relationTypes = [];
diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
index f71c134..39f32d6 100644
--- a/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
+++ b/ui/src/app/entity/relation/relation-type-autocomplete.tpl.html
@@ -26,7 +26,7 @@
md-items="item in fetchRelationTypes(relationTypeSearchText)"
md-item-text="item"
md-min-length="0"
- md-floating-label="{{ 'relation.relation-type' | translate }}"
+ md-floating-label="{{ tbRequired ? ('relation.relation-type' | translate) : ( !relationType ? ('relation.any-relation-type' | translate) : ' ') }}"
md-select-on-match="true"
md-menu-class="tb-relation-type-autocomplete">
<md-item-template>
ui/src/app/event/event.scss 4(+2 -2)
diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss
index 6fa3e67..8622b0d 100644
--- a/ui/src/app/event/event.scss
+++ b/ui/src/app/event/event.scss
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-md-list.tb-table {
+md-list.tb-event-table {
padding: 0px;
md-list-item {
@@ -64,7 +64,7 @@ md-list.tb-table {
}
-#tb-content {
+#tb-event-content {
min-width: 400px;
min-height: 50px;
width: 100%;
diff --git a/ui/src/app/event/event-content-dialog.controller.js b/ui/src/app/event/event-content-dialog.controller.js
index 19ddb8c..235cfcf 100644
--- a/ui/src/app/event/event-content-dialog.controller.js
+++ b/ui/src/app/event/event-content-dialog.controller.js
@@ -62,7 +62,7 @@ export default function EventContentDialogController($mdDialog, content, title,
}
newWidth = 8 * maxLineLength + 16;
}
- $('#tb-content', element).height(newHeight.toString() + "px")
+ $('#tb-event-content', element).height(newHeight.toString() + "px")
.width(newWidth.toString() + "px");
vm.editor.resize();
}
diff --git a/ui/src/app/event/event-content-dialog.tpl.html b/ui/src/app/event/event-content-dialog.tpl.html
index 4cf046b..7b4184c 100644
--- a/ui/src/app/event/event-content-dialog.tpl.html
+++ b/ui/src/app/event/event-content-dialog.tpl.html
@@ -27,7 +27,7 @@
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
- <div flex id="tb-content" readonly
+ <div flex id="tb-event-content" readonly
ui-ace="vm.contentOptions"
ng-model="vm.content">
</div>
diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js
index 5e8cc7c..c43894e 100644
--- a/ui/src/app/event/event-header.directive.js
+++ b/ui/src/app/event/event-header.directive.js
@@ -18,7 +18,6 @@
import eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html';
import eventHeaderStatsTemplate from './event-header-stats.tpl.html';
import eventHeaderErrorTemplate from './event-header-error.tpl.html';
-import eventHeaderAlarmTemplate from './event-header-alarm.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -39,9 +38,6 @@ export default function EventHeaderDirective($compile, $templateCache, types) {
case types.eventType.error.value:
template = eventHeaderErrorTemplate;
break;
- case types.eventType.alarm.value:
- template = eventHeaderAlarmTemplate;
- break;
}
return $templateCache.get(template);
}
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js
index 9bff192..d1feb60 100644
--- a/ui/src/app/event/event-row.directive.js
+++ b/ui/src/app/event/event-row.directive.js
@@ -20,7 +20,6 @@ import eventErrorDialogTemplate from './event-content-dialog.tpl.html';
import eventRowLcEventTemplate from './event-row-lc-event.tpl.html';
import eventRowStatsTemplate from './event-row-stats.tpl.html';
import eventRowErrorTemplate from './event-row-error.tpl.html';
-import eventRowAlarmTemplate from './event-row-alarm.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -41,9 +40,6 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
case types.eventType.error.value:
template = eventRowErrorTemplate;
break;
- case types.eventType.alarm.value:
- template = eventRowAlarmTemplate;
- break;
}
return $templateCache.get(template);
}
diff --git a/ui/src/app/event/event-table.tpl.html b/ui/src/app/event/event-table.tpl.html
index f44aeb3..82a921f 100644
--- a/ui/src/app/event/event-table.tpl.html
+++ b/ui/src/app/event/event-table.tpl.html
@@ -27,7 +27,7 @@
</md-input-container>
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
</section>
- <md-list flex layout="column" class="md-whiteframe-z1 tb-table">
+ <md-list flex layout="column" class="md-whiteframe-z1 tb-event-table">
<md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}">
</md-list>
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!loading()"
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index af04687..86d5240 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -16,7 +16,7 @@
/* eslint-disable import/no-unresolved, import/default */
import importDialogTemplate from './import-dialog.tpl.html';
-import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
+import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -24,7 +24,7 @@ import entityAliasesTemplate from '../entity/entity-aliases.tpl.html';
/* eslint-disable no-undef, angular/window-service, angular/document-service */
/*@ngInject*/
-export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, types, dashboardUtils,
+export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils,
entityService, dashboardService, pluginService, ruleService, widgetService, toast) {
@@ -359,30 +359,47 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
}
function prepareEntityAlias(aliasInfo) {
- var entityFilter;
- var entityType;
+ var alias;
+ var filter;
if (aliasInfo.deviceId) {
- entityFilter = {
- useFilter: false,
- entityNameFilter: '',
- entityList: [aliasInfo.deviceId]
- }
- entityType = types.entityType.device;
+ alias = aliasInfo.aliasName;
+ filter = {
+ type: types.aliasFilterType.entityList.value,
+ entityType: types.entityType.device,
+ entityList: [aliasInfo.deviceId],
+ resolveMultiple: false
+ };
} else if (aliasInfo.deviceFilter) {
- entityFilter = {
- useFilter: aliasInfo.deviceFilter.useFilter,
- entityNameFilter: aliasInfo.deviceFilter.deviceNameFilter,
- entityList: aliasInfo.deviceFilter.deviceList
+ alias = aliasInfo.aliasName;
+ filter = {
+ type: aliasInfo.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
+ entityType: types.entityType.device,
+ resolveMultiple: false
+ }
+ if (filter.type == types.aliasFilterType.entityList.value) {
+ filter.entityList = aliasInfo.deviceFilter.deviceList
+ } else {
+ filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter;
+ }
+ } else if (aliasInfo.entityFilter) {
+ alias = aliasInfo.aliasName;
+ filter = {
+ type: aliasInfo.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
+ entityType: aliasInfo.entityType,
+ resolveMultiple: false
+ }
+ if (filter.type == types.aliasFilterType.entityList.value) {
+ filter.entityList = aliasInfo.entityFilter.entityList;
+ } else {
+ filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter;
}
- entityType = types.entityType.device;
} else {
- entityFilter = aliasInfo.entityFilter;
- entityType = aliasInfo.entityType;
+ alias = aliasInfo.alias;
+ filter = aliasInfo.filter;
}
return {
- aliasName: aliasInfo.aliasName,
- entityType: entityType,
- entityFilter: entityFilter
+ alias: alias,
+ filter: filter
};
}
@@ -395,6 +412,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
deferred.reject();
} else {
var widget = widgetItem.widget;
+ widget = dashboardUtils.validateAndUpdateWidget(widget);
var aliasesInfo = prepareAliasesInfo(widgetItem.aliasesInfo);
var originalColumns = widgetItem.originalColumns;
var originalSize = widgetItem.originalSize;
@@ -405,20 +423,22 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var entityAliases = {};
var datasourceAliasesMap = {};
var targetDeviceAliasesMap = {};
- var aliasId = 1;
+ var aliasId;
var datasourceIndex;
if (datasourceAliases) {
for (datasourceIndex in datasourceAliases) {
+ aliasId = utils.guid();
datasourceAliasesMap[aliasId] = datasourceIndex;
entityAliases[aliasId] = datasourceAliases[datasourceIndex];
- aliasId++;
+ entityAliases[aliasId].id = aliasId;
}
}
if (targetDeviceAliases) {
for (datasourceIndex in targetDeviceAliases) {
+ aliasId = utils.guid();
targetDeviceAliasesMap[aliasId] = datasourceIndex;
entityAliases[aliasId] = targetDeviceAliases[datasourceIndex];
- aliasId++;
+ entityAliases[aliasId].id = aliasId;
}
}
@@ -435,12 +455,10 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
var datasourceIndex;
if (datasourceAliasesMap[aliasId]) {
datasourceIndex = datasourceAliasesMap[aliasId];
- datasourceAliases[datasourceIndex].entityType = entityAlias.entityType;
- datasourceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter;
+ datasourceAliases[datasourceIndex] = entityAlias;
} else if (targetDeviceAliasesMap[aliasId]) {
datasourceIndex = targetDeviceAliasesMap[aliasId];
- targetDeviceAliases[datasourceIndex].entityType = entityAlias.entityType;
- targetDeviceAliases[datasourceIndex].entityFilter = entityAlias.entityFilter;
+ targetDeviceAliases[datasourceIndex] = entityAlias;
}
}
addImportedWidget(dashboard, targetState, targetLayoutFunction, $event, widget,
@@ -622,7 +640,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred);
} else {
var missingEntityAlias = angular.copy(entityAlias);
- missingEntityAlias.entityFilter = null;
+ missingEntityAlias.filter = null;
missingEntityAliases[aliasId] = missingEntityAlias;
checkNextEntityAliasOrComplete(index, aliasIds, entityAliases, missingEntityAliases, deferred);
}
@@ -641,8 +659,6 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
entityAliases: missingEntityAliases,
widgets: widgets,
isSingleWidget: isSingleWidget,
- isSingleEntityAlias: false,
- singleEntityAlias: null,
customTitle: customTitle,
disableAdd: true
}
ui/src/app/layout/index.js 4(+4 -0)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index be63558..2a27c93 100644
--- a/ui/src/app/layout/index.js
+++ b/ui/src/app/layout/index.js
@@ -32,6 +32,8 @@ import thingsboardDashboardAutocomplete from '../components/dashboard-autocomple
import thingsboardUserMenu from './user-menu.directive';
import thingsboardEntity from '../entity';
+import thingsboardEvent from '../event';
+import thingsboardAlarm from '../alarm';
import thingsboardTenant from '../tenant';
import thingsboardCustomer from '../customer';
import thingsboardUser from '../user';
@@ -61,6 +63,8 @@ export default angular.module('thingsboard.home', [
thingsboardHomeLinks,
thingsboardUserMenu,
thingsboardEntity,
+ thingsboardEvent,
+ thingsboardAlarm,
thingsboardTenant,
thingsboardCustomer,
thingsboardUser,
ui/src/app/locale/locale.constant.js 149(+137 -12)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 6bc6e7e..a54eba4 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -108,9 +108,79 @@ export default angular.module('thingsboard.locale', [])
},
"alarm": {
"alarm": "Alarm",
+ "alarms": "Alarms",
"select-alarm": "Select alarm",
"no-alarms-matching": "No alarms matching '{{entity}}' were found.",
- "alarm-required": "Alarm is required"
+ "alarm-required": "Alarm is required",
+ "alarm-status": "Alarm status",
+ "search-status": {
+ "ANY": "Any",
+ "ACTIVE": "Active",
+ "CLEARED": "Cleared",
+ "ACK": "Acknowledged",
+ "UNACK": "Unacknowledged"
+ },
+ "display-status": {
+ "ACTIVE_UNACK": "Active Unacknowledged",
+ "ACTIVE_ACK": "Active Acknowledged",
+ "CLEARED_UNACK": "Cleared Unacknowledged",
+ "CLEARED_ACK": "Cleared Acknowledged"
+ },
+ "no-alarms-prompt": "No alarms found",
+ "created-time": "Created time",
+ "type": "Type",
+ "severity": "Severity",
+ "originator": "Originator",
+ "details": "Details",
+ "status": "Status",
+ "alarm-details": "Alarm details",
+ "start-time": "Start time",
+ "end-time": "End time",
+ "ack-time": "Acknowledged time",
+ "clear-time": "Cleared time",
+ "severity-critical": "Critical",
+ "severity-major": "Major",
+ "severity-minor": "Minor",
+ "severity-warning": "Warning",
+ "severity-indeterminate": "Indeterminate",
+ "acknowledge": "Acknowledge",
+ "clear": "Clear"
+ },
+ "alias": {
+ "add": "Add alias",
+ "edit": "Edit alias",
+ "name": "Alias name",
+ "name-required": "Alias name is required",
+ "duplicate-alias": "Alias with same name is already exists.",
+ "filter-type-entity-list": "Entity list",
+ "filter-type-entity-name": "Entity name",
+ "filter-type-state-entity": "Entity from dashboard state",
+ "filter-type-state-entity-description": "Entity taken from dashboard state parameters",
+ "filter-type-asset-type": "Asset type",
+ "filter-type-asset-type-description": "Assets of type '{{assetType}}'",
+ "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'",
+ "filter-type-device-type": "Device type",
+ "filter-type-device-type-description": "Devices of type '{{deviceType}}'",
+ "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'",
+ "filter-type-relations-query": "Relations query",
+ "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "filter-type-asset-search-query": "Asset search query",
+ "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "filter-type-device-search-query": "Device search query",
+ "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "entity-filter": "Entity filter",
+ "resolve-multiple": "Resolve as multiple entities",
+ "filter-type": "Filter type",
+ "filter-type-required": "Filter type is required.",
+ "entity-filter-no-entity-matched": "No entities matching specified filter were found.",
+ "no-entity-filter-specified": "No entity filter specified",
+ "root-state-entity": "Use dashboard state entity as root",
+ "root-entity": "Root entity",
+ "max-relation-level": "Max relation level",
+ "unlimited-level": "Unlimited level",
+ "state-entity": "Dashboard state entity",
+ "all-entities": "All entities",
+ "any-relation": "any"
},
"asset": {
"asset": "Asset",
@@ -133,6 +203,11 @@ export default angular.module('thingsboard.locale', [])
"asset-type": "Asset type",
"asset-type-required": "Asset type is required.",
"select-asset-type": "Select asset type",
+ "enter-asset-type": "Enter asset type",
+ "any-asset": "Any asset",
+ "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.",
+ "asset-type-list-empty": "No asset types selected.",
+ "asset-types": "Asset types",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -166,7 +241,8 @@ export default angular.module('thingsboard.locale', [])
"idCopiedMessage": "Asset Id has been copied to clipboard",
"select-asset": "Select asset",
"no-assets-matching": "No assets matching '{{entity}}' were found.",
- "asset-required": "Asset is required"
+ "asset-required": "Asset is required",
+ "name-starts-with": "Asset name starts with"
},
"attribute": {
"attributes": "Attributes",
@@ -417,6 +493,7 @@ export default angular.module('thingsboard.locale', [])
},
"datasource": {
"type": "Datasource type",
+ "name": "Name",
"add-datasource-prompt": "Please add datasource"
},
"details": {
@@ -444,7 +521,7 @@ export default angular.module('thingsboard.locale', [])
"alias-required": "Device alias is required.",
"remove-alias": "Remove device alias",
"add-alias": "Add device alias",
- "name-starts-with": "Name starts with",
+ "name-starts-with": "Device name starts with",
"device-list": "Device list",
"use-device-name-filter": "Use filter",
"device-list-empty": "No devices selected.",
@@ -497,6 +574,11 @@ export default angular.module('thingsboard.locale', [])
"device-type": "Device type",
"device-type-required": "Device type is required.",
"select-device-type": "Select device type",
+ "enter-device-type": "Enter device type",
+ "any-device": "Any device",
+ "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.",
+ "device-type-list-empty": "No device types selected.",
+ "device-types": "Device types",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -530,41 +612,75 @@ export default angular.module('thingsboard.locale', [])
"unable-delete-entity-alias-title": "Unable to delete entity alias",
"unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
"duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Entity aliases must be unique whithin the dashboard.",
+ "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
"configure-alias": "Configure '{{alias}}' alias",
"alias": "Alias",
"alias-required": "Entity alias is required.",
"remove-alias": "Remove entity alias",
"add-alias": "Add entity alias",
"entity-list": "Entity list",
+ "entity-type": "Entity type",
+ "entity-types": "Entity types",
+ "entity-type-list": "Entity type list",
+ "any-entity": "Any entity",
+ "enter-entity-type": "Enter entity type",
"no-entities-matching": "No entities matching '{{entity}}' were found.",
+ "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.",
"name-starts-with": "Name starts with",
"use-entity-name-filter": "Use filter",
"entity-list-empty": "No entities selected.",
+ "entity-type-list-empty": "No entity types selected.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
"all-subtypes": "All",
+ "select-entities": "Select entities",
+ "no-aliases-found": "No aliases found.",
+ "no-alias-matching": "'{{alias}}' not found.",
+ "create-new-alias": "Create a new one!",
+ "no-keys-found": "No keys found.",
+ "no-key-matching": "'{{key}}' not found.",
+ "create-new-key": "Create a new one!",
"type": "Type",
"type-required": "Entity type is required.",
"type-device": "Device",
+ "type-devices": "Devices",
+ "list-of-devices": "{ count, select, 1 {One device} other {List of # devices} }",
+ "device-name-starts-with": "Devices whose names start with '{{prefix}}'",
"type-asset": "Asset",
+ "type-assets": "Assets",
+ "list-of-assets": "{ count, select, 1 {One asset} other {List of # assets} }",
+ "asset-name-starts-with": "Assets whose names start with '{{prefix}}'",
"type-rule": "Rule",
+ "type-rules": "Rules",
+ "list-of-rules": "{ count, select, 1 {One rule} other {List of # rules} }",
+ "rule-name-starts-with": "Rules whose names start with '{{prefix}}'",
"type-plugin": "Plugin",
+ "type-plugins": "Plugins",
+ "list-of-plugins": "{ count, select, 1 {One plugin} other {List of # plugins} }",
+ "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'",
"type-tenant": "Tenant",
+ "type-tenants": "Tenants",
+ "list-of-tenants": "{ count, select, 1 {One tenant} other {List of # tenants} }",
+ "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
"type-customer": "Customer",
+ "type-customers": "Customers",
+ "list-of-customers": "{ count, select, 1 {One customer} other {List of # customers} }",
+ "customer-name-starts-with": "Customers whose names start with '{{prefix}}'",
"type-user": "User",
+ "type-users": "Users",
+ "list-of-users": "{ count, select, 1 {One user} other {List of # users} }",
+ "user-name-starts-with": "Users whose names start with '{{prefix}}'",
"type-dashboard": "Dashboard",
+ "type-dashboards": "Dashboards",
+ "list-of-dashboards": "{ count, select, 1 {One dashboard} other {List of # dashboards} }",
+ "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'",
"type-alarm": "Alarm",
- "select-entities": "Select entities",
- "no-aliases-found": "No aliases found.",
- "no-alias-matching": "'{{alias}}' not found.",
- "create-new-alias": "Create a new one!",
- "no-keys-found": "No keys found.",
- "no-key-matching": "'{{key}}' not found.",
- "create-new-key": "Create a new one!"
+ "type-alarms": "Alarms",
+ "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
+ "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'"
},
"event": {
"event-type": "Event type",
- "type-alarm": "Alarm",
"type-error": "Error",
"type-lc-event": "Lifecycle event",
"type-stats": "Statistics",
@@ -724,6 +840,10 @@ export default angular.module('thingsboard.locale', [])
"FROM": "From",
"TO": "To"
},
+ "direction-type": {
+ "FROM": "from",
+ "TO": "to"
+ },
"from-relations": "Outbound relations",
"to-relations": "Inbound relations",
"selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected",
@@ -737,6 +857,7 @@ export default angular.module('thingsboard.locale', [])
"delete": "Delete relation",
"relation-type": "Relation type",
"relation-type-required": "Relation type is required.",
+ "any-relation-type": "Any type",
"add": "Add relation",
"delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
"delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.",
@@ -745,7 +866,11 @@ export default angular.module('thingsboard.locale', [])
"delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
"delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
"delete-from-relations-title": "Are you sure you want to delete { count, select, 1 {1 relation} other {# relations} }?",
- "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities."
+ "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.",
+ "remove-relation-filter": "Remove relation filter",
+ "add-relation-filter": "Add relation filter",
+ "any-relation": "Any relation",
+ "relation-filters": "Relation filters"
},
"rule": {
"rule": "Rule",
diff --git a/ui/src/app/locale/locale.constant-es.js b/ui/src/app/locale/locale.constant-es.js
index e4d4b49..b9076ba 100644
--- a/ui/src/app/locale/locale.constant-es.js
+++ b/ui/src/app/locale/locale.constant-es.js
@@ -411,7 +411,6 @@
},
"event": {
"event-type": "Tipo de evento",
- "type-alarm": "Alarma",
"type-error": "Error",
"type-lc-event": "Ciclo de vida",
"type-stats": "Estadísticas",
diff --git a/ui/src/app/locale/locale.constant-ko.js b/ui/src/app/locale/locale.constant-ko.js
index 32dbf49..0caeab8 100644
--- a/ui/src/app/locale/locale.constant-ko.js
+++ b/ui/src/app/locale/locale.constant-ko.js
@@ -378,7 +378,6 @@ export default function addLocaleKorean(locales) {
},
"event": {
"event-type": "이벤트 타입",
- "type-alarm": "알람",
"type-error": "에러",
"type-lc-event": "주기적 이벤트",
"type-stats": "통계",
diff --git a/ui/src/app/locale/locale.constant-ru.js b/ui/src/app/locale/locale.constant-ru.js
index d7734b4..cded7a6 100644
--- a/ui/src/app/locale/locale.constant-ru.js
+++ b/ui/src/app/locale/locale.constant-ru.js
@@ -411,7 +411,6 @@ export default function addLocaleRussian(locales) {
},
"event": {
"event-type": "Тип события",
- "type-alarm": "Аварийное оповещение",
"type-error": "Ошибка",
"type-lc-event": "Событие жизненного цикла",
"type-stats": "Статистика",
diff --git a/ui/src/app/locale/locale.constant-zh.js b/ui/src/app/locale/locale.constant-zh.js
index 3246070..62746c7 100644
--- a/ui/src/app/locale/locale.constant-zh.js
+++ b/ui/src/app/locale/locale.constant-zh.js
@@ -411,7 +411,6 @@ export default function addLocaleChinese(locales) {
},
"event" : {
"event-type": "事件类型",
- "type-alarm": "报警",
"type-error": "错误",
"type-lc-event": "生命周期事件",
"type-stats": "类型统计",
ui/src/app/plugin/index.js 2(+0 -2)
diff --git a/ui/src/app/plugin/index.js b/ui/src/app/plugin/index.js
index 1c9e731..6173d62 100644
--- a/ui/src/app/plugin/index.js
+++ b/ui/src/app/plugin/index.js
@@ -16,7 +16,6 @@
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardJsonForm from '../components/json-form.directive';
-import thingsboardEvent from '../event';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
@@ -28,7 +27,6 @@ export default angular.module('thingsboard.plugin', [
uiRouter,
thingsboardGrid,
thingsboardJsonForm,
- thingsboardEvent,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor
])
ui/src/app/plugin/plugins.tpl.html 8(+6 -2)
diff --git a/ui/src/app/plugin/plugins.tpl.html b/ui/src/app/plugin/plugins.tpl.html
index 5b03506..73b0adc 100644
--- a/ui/src/app/plugin/plugins.tpl.html
+++ b/ui/src/app/plugin/plugins.tpl.html
@@ -48,12 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.plugin"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'plugin.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.plugin"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.lcEvent.value}}"
- disabled-event-types="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.lcEvent.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">
ui/src/app/rule/index.js 2(+0 -2)
diff --git a/ui/src/app/rule/index.js b/ui/src/app/rule/index.js
index 6481920..2700ec7 100644
--- a/ui/src/app/rule/index.js
+++ b/ui/src/app/rule/index.js
@@ -17,7 +17,6 @@ import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardPluginSelect from '../components/plugin-select.directive';
import thingsboardComponent from '../component';
-import thingsboardEvent from '../event';
import thingsboardApiRule from '../api/rule.service';
import thingsboardApiPlugin from '../api/plugin.service';
import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
@@ -31,7 +30,6 @@ export default angular.module('thingsboard.rule', [
thingsboardGrid,
thingsboardPluginSelect,
thingsboardComponent,
- thingsboardEvent,
thingsboardApiRule,
thingsboardApiPlugin,
thingsboardApiComponentDescriptor
ui/src/app/rule/rules.tpl.html 8(+6 -2)
diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html
index 098bbee..336fe63 100644
--- a/ui/src/app/rule/rules.tpl.html
+++ b/ui/src/app/rule/rules.tpl.html
@@ -48,12 +48,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.rule"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'rule.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.rule"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
- default-event-type="{{vm.types.eventType.lcEvent.value}}"
- disabled-event-types="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.lcEvent.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem())" label="{{ 'relation.relations' | translate }}">
ui/src/app/services/item-buffer.service.js 37(+12 -25)
diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js
index afb63bf..d29ad18 100644
--- a/ui/src/app/services/item-buffer.service.js
+++ b/ui/src/app/services/item-buffer.service.js
@@ -46,16 +46,14 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
aliasesInfo {
datasourceAliases: {
datasourceIndex: {
- aliasName: "...",
- entityType: "...",
- entityFilter: "..."
+ alias: "...",
+ filter: "..."
}
}
targetDeviceAliases: {
targetDeviceAliasIndex: {
- aliasName: "...",
- entityType: "...",
- entityFilter: "..."
+ alias: "...",
+ filter: "..."
}
}
....
@@ -64,9 +62,8 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
function prepareAliasInfo(entityAlias) {
return {
- aliasName: entityAlias.alias,
- entityType: entityAlias.entityType,
- entityFilter: entityAlias.entityFilter
+ alias: entityAlias.alias,
+ filter: entityAlias.filter
};
}
@@ -264,14 +261,9 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
dashboardUtils.addWidgetToLayout(theDashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column);
if (callAliasUpdateFunction) {
- onAliasesUpdateFunction().then(
- function() {
- deferred.resolve(theDashboard);
- }
- );
- } else {
- deferred.resolve(theDashboard);
+ onAliasesUpdateFunction();
}
+ deferred.resolve(theDashboard);
return deferred.promise;
}
@@ -293,8 +285,7 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
function isEntityAliasEqual(alias1, alias2) {
- return alias1.entityType === alias2.entityType &&
- angular.equals(alias1.entityFilter, alias2.entityFilter);
+ return angular.equals(alias1.filter, alias2.filter);
}
function getEntityAliasId(entityAliases, aliasInfo) {
@@ -306,13 +297,9 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
}
}
if (!newAliasId) {
- var newAliasName = createEntityAliasName(entityAliases, aliasInfo.aliasName);
- newAliasId = 0;
- for (aliasId in entityAliases) {
- newAliasId = Math.max(newAliasId, aliasId);
- }
- newAliasId++;
- entityAliases[newAliasId] = {alias: newAliasName, entityType: aliasInfo.entityType, entityFilter: aliasInfo.entityFilter};
+ var newAliasName = createEntityAliasName(entityAliases, aliasInfo.alias);
+ newAliasId = utils.guid();
+ entityAliases[newAliasId] = {id: newAliasId, alias: newAliasName, filter: aliasInfo.filter};
}
return newAliasId;
}
ui/src/app/tenant/tenants.tpl.html 7(+6 -1)
diff --git a/ui/src/app/tenant/tenants.tpl.html b/ui/src/app/tenant/tenants.tpl.html
index 00350a4..e407291 100644
--- a/ui/src/app/tenant/tenants.tpl.html
+++ b/ui/src/app/tenant/tenants.tpl.html
@@ -46,11 +46,16 @@
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
+ <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'alarm.alarms' | translate }}">
+ <tb-alarm-table flex entity-type="vm.types.entityType.tenant"
+ entity-id="vm.grid.operatingItem().id.id">
+ </tb-alarm-table>
+ </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'tenant.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.tenant"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.types.id.nullUid"
- default-event-type="{{vm.types.eventType.alarm.value}}">
+ default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'relation.relations' | translate }}">
ui/src/scss/main.scss 27(+27 -0)
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index dbe3543..ab720c5 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -236,6 +236,15 @@ div {
}
}
+.md-caption {
+ &.tb-required:after {
+ content: ' *';
+ font-size: 10px;
+ vertical-align: top;
+ color: rgba(0,0,0,0.54);
+ }
+}
+
pre.tb-highlight {
background-color: #f7f7f7;
display: block;
@@ -300,6 +309,24 @@ pre.tb-highlight {
}
}
+.tb-severity {
+ font-weight: bold;
+ &.tb-critical {
+ color: red !important;
+ }
+ &.tb-major {
+ color: orange !important;
+ }
+ &.tb-minor {
+ color: #ffca3d !important;
+ }
+ &.tb-warning {
+ color: #abab00 !important;
+ }
+ &.tb-indeterminate {
+ color: green !important;
+ }
+}
/***********************
* Flow