Commit b5659fd32d06cd090f25c9eefce8898aac1c2e48

Authored by Bohdan Smetaniuk
2 parents 7f848b32 c6e1b471

Merge remote-tracking branch 'upstream/feature/edge' into feature/widgets_bundle_fetch

Showing 25 changed files with 295 additions and 58 deletions
... ... @@ -66,6 +66,7 @@ import org.thingsboard.server.dao.user.UserService;
66 66 import org.thingsboard.server.queue.discovery.PartitionService;
67 67 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
68 68 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  69 +import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
69 70 import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
70 71 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
71 72 import org.thingsboard.server.service.executors.ExternalCallExecutorService;
... ... @@ -254,15 +255,14 @@ public class ActorSystemContext {
254 255 @Getter
255 256 private TbCoreDeviceRpcService tbCoreDeviceRpcService;
256 257
257   - @Lazy
258   - @Autowired
259   - @Getter
260   - private EdgeService edgeService;
  258 + @Autowired(required = false)
  259 + @Getter private EdgeService edgeService;
261 260
262   - @Lazy
263   - @Autowired
264   - @Getter
265   - private EdgeEventService edgeEventService;
  261 + @Autowired(required = false)
  262 + @Getter private EdgeEventService edgeEventService;
  263 +
  264 + @Autowired(required = false)
  265 + @Getter private EdgeRpcService edgeRpcService;
266 266
267 267 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
268 268 @Getter
... ...
... ... @@ -31,10 +31,13 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
31 31 import org.thingsboard.server.actors.service.DefaultActorService;
32 32 import org.thingsboard.server.common.data.EntityType;
33 33 import org.thingsboard.server.common.data.Tenant;
  34 +import org.thingsboard.server.common.data.edge.Edge;
34 35 import org.thingsboard.server.common.data.id.DeviceId;
  36 +import org.thingsboard.server.common.data.id.EdgeId;
35 37 import org.thingsboard.server.common.data.id.EntityId;
36 38 import org.thingsboard.server.common.data.id.RuleChainId;
37 39 import org.thingsboard.server.common.data.id.TenantId;
  40 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
38 41 import org.thingsboard.server.common.data.rule.RuleChain;
39 42 import org.thingsboard.server.common.data.rule.RuleChainType;
40 43 import org.thingsboard.server.common.msg.MsgType;
... ... @@ -47,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
47 50 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
48 51 import org.thingsboard.server.common.msg.queue.RuleEngineException;
49 52 import org.thingsboard.server.common.msg.queue.ServiceType;
  53 +import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
50 54
51 55 import java.util.List;
52 56 import java.util.Optional;
... ... @@ -202,7 +206,18 @@ public class TenantActor extends RuleChainManagerActor {
202 206 }
203 207
204 208 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
205   - if (isRuleEngineForCurrentTenant) {
  209 + if (msg.getEntityId().getEntityType() == EntityType.EDGE) {
  210 + EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
  211 + EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
  212 + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
  213 + edgeRpcService.deleteEdge(edgeId);
  214 + } else {
  215 + Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
  216 + if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
  217 + edgeRpcService.updateEdge(edge);
  218 + }
  219 + }
  220 + } else if (isRuleEngineForCurrentTenant) {
206 221 TbActorRef target = getEntityActorRef(msg.getEntityId());
207 222 if (target != null) {
208 223 if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
... ...
... ... @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
40 40 import org.thingsboard.server.common.data.id.TenantId;
41 41 import org.thingsboard.server.common.data.page.TextPageData;
42 42 import org.thingsboard.server.common.data.page.TextPageLink;
  43 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
43 44 import org.thingsboard.server.common.data.rule.RuleChain;
44 45 import org.thingsboard.server.dao.exception.DataValidationException;
45 46 import org.thingsboard.server.dao.exception.IncorrectParameterException;
... ... @@ -101,6 +102,9 @@ public class EdgeController extends BaseController {
101 102 edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
102 103 }
103 104
  105 + tbClusterService.onEntityStateChange(savedEdge.getTenantId(), savedEdge.getId(),
  106 + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  107 +
104 108 logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
105 109 return savedEdge;
106 110 } catch (Exception e) {
... ... @@ -120,6 +124,9 @@ public class EdgeController extends BaseController {
120 124 Edge edge = checkEdgeId(edgeId, Operation.DELETE);
121 125 edgeService.deleteEdge(getTenantId(), edgeId);
122 126
  127 + tbClusterService.onEntityStateChange(getTenantId(), edgeId,
  128 + ComponentLifecycleEvent.DELETED);
  129 +
123 130 logEntityAction(edgeId, edge,
124 131 null,
125 132 ActionType.DELETED, null, strEdgeId);
... ... @@ -284,6 +291,8 @@ public class EdgeController extends BaseController {
284 291
285 292 Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId);
286 293
  294 + tbClusterService.onEntityStateChange(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED);
  295 +
287 296 logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);
288 297
289 298 return updatedEdge;
... ...
... ... @@ -61,7 +61,7 @@ public class EdgeEventController extends BaseController {
61 61 EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
62 62 checkEdgeId(edgeId, Operation.READ);
63 63 TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
64   - return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink));
  64 + return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false));
65 65 } catch (Exception e) {
66 66 throw handleException(e);
67 67 }
... ...
... ... @@ -111,7 +111,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
111 111
112 112 @Override
113 113 public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) {
114   - return edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink);
  114 + return edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true);
115 115 }
116 116
117 117 @Override
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
27 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
28 28 import org.springframework.stereotype.Service;
29 29 import org.thingsboard.server.common.data.DataConstants;
  30 +import org.thingsboard.server.common.data.edge.Edge;
30 31 import org.thingsboard.server.common.data.id.EdgeId;
31 32 import org.thingsboard.server.common.data.id.TenantId;
32 33 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
... ... @@ -52,7 +53,7 @@ import java.util.concurrent.Executors;
52 53 @Service
53 54 @Slf4j
54 55 @ConditionalOnProperty(prefix = "edges.rpc", value = "enabled", havingValue = "true")
55   -public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase {
  56 +public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase implements EdgeRpcService {
56 57
57 58 private final Map<EdgeId, EdgeGrpcSession> sessions = new ConcurrentHashMap<>();
58 59 private static final ObjectMapper mapper = new ObjectMapper();
... ... @@ -117,6 +118,23 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase {
117 118 return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper).getInputStream();
118 119 }
119 120
  121 + @Override
  122 + public void updateEdge(Edge edge) {
  123 + EdgeGrpcSession session = sessions.get(edge.getId());
  124 + if (session != null && session.isConnected()) {
  125 + session.onConfigurationUpdate(edge);
  126 + }
  127 + }
  128 +
  129 + @Override
  130 + public void deleteEdge(EdgeId edgeId) {
  131 + EdgeGrpcSession session = sessions.get(edgeId);
  132 + if (session != null && session.isConnected()) {
  133 + session.close();
  134 + sessions.remove(edgeId);
  135 + }
  136 + }
  137 +
120 138 private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) {
121 139 sessions.put(edgeId, edgeGrpcSession);
122 140 save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true);
... ...
... ... @@ -198,6 +198,20 @@ public final class EdgeGrpcSession implements Closeable {
198 198 };
199 199 }
200 200
  201 + void onConfigurationUpdate(Edge edge) {
  202 + try {
  203 + this.edge = edge;
  204 + // TODO: voba - push edge configuration update to edge
  205 +// outputStream.onNext(org.thingsboard.server.gen.integration.ResponseMsg.newBuilder()
  206 +// .setIntegrationUpdateMsg(IntegrationUpdateMsg.newBuilder()
  207 +// .setConfiguration(constructIntegrationConfigProto(configuration, defaultConverterProto, downLinkConverterProto))
  208 +// .build())
  209 +// .build());
  210 + } catch (Exception e) {
  211 + log.error("Failed to construct proto objects!", e);
  212 + }
  213 + }
  214 +
201 215 void processHandleMessages() throws ExecutionException, InterruptedException {
202 216 Long queueStartTs = getQueueStartTs().get();
203 217 TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), queueStartTs, null, true);
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.edge.rpc;
  17 +
  18 +import org.thingsboard.server.common.data.edge.Edge;
  19 +import org.thingsboard.server.common.data.id.EdgeId;
  20 +
  21 +public interface EdgeRpcService {
  22 +
  23 + void updateEdge(Edge edge);
  24 +
  25 + void deleteEdge(EdgeId edgeId);
  26 +}
... ...
... ... @@ -16,9 +16,7 @@
16 16 package org.thingsboard.server.dao.edge;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19   -import org.thingsboard.server.common.data.EntityType;
20 19 import org.thingsboard.server.common.data.edge.EdgeEvent;
21   -import org.thingsboard.server.common.data.edge.EdgeEventType;
22 20 import org.thingsboard.server.common.data.id.EdgeId;
23 21 import org.thingsboard.server.common.data.id.TenantId;
24 22 import org.thingsboard.server.common.data.page.TimePageData;
... ... @@ -28,6 +26,5 @@ public interface EdgeEventService {
28 26
29 27 ListenableFuture<EdgeEvent> saveAsync(EdgeEvent edgeEvent);
30 28
31   - TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
32   -
  29 + TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate);
33 30 }
... ...
... ... @@ -35,7 +35,7 @@ import java.util.List;
35 35 public class BaseEdgeEventService implements EdgeEventService {
36 36
37 37 @Autowired
38   - public EdgeEventDao edgeEventDao;
  38 + private EdgeEventDao edgeEventDao;
39 39
40 40 @Override
41 41 public ListenableFuture<EdgeEvent> saveAsync(EdgeEvent edgeEvent) {
... ... @@ -44,8 +44,8 @@ public class BaseEdgeEventService implements EdgeEventService {
44 44 }
45 45
46 46 @Override
47   - public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) {
48   - List<EdgeEvent> events = edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, pageLink);
  47 + public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) {
  48 + List<EdgeEvent> events = edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, pageLink, withTsUpdate);
49 49 return new TimePageData<>(events, pageLink);
50 50 }
51 51
... ...
... ... @@ -53,7 +53,7 @@ public class CassandraEdgeEventDao extends CassandraAbstractSearchTimeDao<EdgeEv
53 53 }
54 54
55 55 @Override
56   - public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink) {
  56 + public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) {
57 57 return null;
58 58 }
59 59 }
... ...
... ... @@ -46,6 +46,6 @@ public interface EdgeEventDao extends Dao<EdgeEvent> {
46 46 * @param pageLink the pageLink
47 47 * @return the event list
48 48 */
49   - List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink);
  49 + List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate);
50 50
51 51 }
... ...
... ... @@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.Specification;
26 26 import org.springframework.data.repository.CrudRepository;
27 27 import org.springframework.stereotype.Component;
28 28 import org.thingsboard.server.common.data.UUIDConverter;
  29 +import org.thingsboard.server.common.data.audit.ActionType;
29 30 import org.thingsboard.server.common.data.edge.EdgeEvent;
30 31 import org.thingsboard.server.common.data.id.EdgeEventId;
31 32 import org.thingsboard.server.common.data.id.EdgeId;
... ... @@ -75,9 +76,9 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit
75 76 }
76 77
77 78 @Override
78   - public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink) {
  79 + public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) {
79 80 Specification<EdgeEventEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id");
80   - Specification<EdgeEventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, edgeId);
  81 + Specification<EdgeEventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, edgeId, withTsUpdate);
81 82 Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC;
82 83 Pageable pageable = PageRequest.of(0, pageLink.getLimit(), sortDirection, ID_PROPERTY);
83 84 return DaoUtil.convertDataList(edgeEventRepository.findAll(Specification.where(timeSearchSpec).and(fieldsSpec), pageable).getContent());
... ... @@ -95,7 +96,7 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit
95 96 return Optional.of(DaoUtil.getData(edgeEventRepository.save(entity)));
96 97 }
97 98
98   - private Specification<EdgeEventEntity> getEntityFieldsSpec(UUID tenantId, EdgeId edgeId) {
  99 + private Specification<EdgeEventEntity> getEntityFieldsSpec(UUID tenantId, EdgeId edgeId, boolean withTsUpdate) {
99 100 return (root, criteriaQuery, criteriaBuilder) -> {
100 101 List<Predicate> predicates = new ArrayList<>();
101 102 if (tenantId != null) {
... ... @@ -106,6 +107,10 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit
106 107 Predicate entityIdPredicate = criteriaBuilder.equal(root.get("edgeId"), UUIDConverter.fromTimeUUID(edgeId.getId()));
107 108 predicates.add(entityIdPredicate);
108 109 }
  110 + if (!withTsUpdate) {
  111 + Predicate edgeEventActionPredicate = criteriaBuilder.notEqual(root.get("edgeEventAction"), ActionType.TIMESERIES_UPDATED.name());
  112 + predicates.add(edgeEventActionPredicate);
  113 + }
109 114 return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
110 115 };
111 116 }
... ...
... ... @@ -19,6 +19,7 @@ import com.datastax.driver.core.utils.UUIDs;
19 19 import org.junit.Assert;
20 20 import org.junit.Test;
21 21 import org.thingsboard.server.common.data.DataConstants;
  22 +import org.thingsboard.server.common.data.audit.ActionType;
22 23 import org.thingsboard.server.common.data.edge.EdgeEvent;
23 24 import org.thingsboard.server.common.data.edge.EdgeEventType;
24 25 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -82,7 +83,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
82 83 EdgeEvent savedEdgeEvent3 = saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId);
83 84 saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId);
84 85
85   - TimePageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(2, startTime, endTime, false));
  86 + TimePageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(2, startTime, endTime, false), true);
86 87
87 88 Assert.assertNotNull(edgeEvents.getData());
88 89 Assert.assertTrue(edgeEvents.getData().size() == 2);
... ... @@ -91,7 +92,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
91 92 Assert.assertTrue(edgeEvents.hasNext());
92 93 Assert.assertNotNull(edgeEvents.getNextPageLink());
93 94
94   - edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, edgeEvents.getNextPageLink());
  95 + edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, edgeEvents.getNextPageLink(), true);
95 96
96 97 Assert.assertNotNull(edgeEvents.getData());
97 98 Assert.assertTrue(edgeEvents.getData().size() == 1);
... ... @@ -100,6 +101,26 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
100 101 Assert.assertNull(edgeEvents.getNextPageLink());
101 102 }
102 103
  104 + @Test
  105 + public void findEdgeEventsWithTsUpdateAndWithout() throws Exception {
  106 + EdgeId edgeId = new EdgeId(UUIDs.timeBased());
  107 + DeviceId deviceId = new DeviceId(UUIDs.timeBased());
  108 + TenantId tenantId = new TenantId(UUIDs.timeBased());
  109 + TimePageLink pageLink = new TimePageLink(1);
  110 +
  111 + EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, ActionType.TIMESERIES_UPDATED.name());
  112 + edgeEventService.saveAsync(edgeEventWithTsUpdate);
  113 +
  114 + TimePageData<EdgeEvent> allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true);
  115 + TimePageData<EdgeEvent> edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false);
  116 +
  117 + Assert.assertNotNull(allEdgeEvents.getData());
  118 + Assert.assertNotNull(edgeEventsWithoutTsUpdate.getData());
  119 + Assert.assertEquals(1, allEdgeEvents.getData().size());
  120 + Assert.assertEquals(allEdgeEvents.getData().get(0).getUuidId(), edgeEventWithTsUpdate.getUuidId());
  121 + Assert.assertTrue(edgeEventsWithoutTsUpdate.getData().isEmpty());
  122 + }
  123 +
103 124 private EdgeEvent saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception {
104 125 EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, DataConstants.ENTITY_CREATED);
105 126 edgeEvent.setId(new EdgeEventId(UUIDs.startOf(time)));
... ...
... ... @@ -56,7 +56,7 @@ function EdgeService($http, $q, customerService) {
56 56 deferred.reject();
57 57 });
58 58 return deferred.promise;
59   - } // TODO: deaflynx: check usage in UI
  59 + }
60 60
61 61 function getEdgesByIds(edgeIds, config) {
62 62 var deferred = $q.defer();
... ...
... ... @@ -389,6 +389,25 @@ export default angular.module('thingsboard.types', [])
389 389 customer: "CUSTOMER",
390 390 relation: "RELATION"
391 391 },
  392 + edgeEventAction: {
  393 + updated: "UPDATED",
  394 + added: "ADDED",
  395 + assignedToEdge: "ASSIGNED_TO_EDGE",
  396 + deleted: "DELETED",
  397 + unassignedFromEdge: "UNASSIGNED_FROM_EDGE",
  398 + alarmAck: "ALARM_ACK",
  399 + alarmClear: "ALARM_CLEAR",
  400 + credentialsUpdated: "CREDENTIALS_UPDATED",
  401 + attributesUpdated: "ATTRIBUTES_UPDATED",
  402 + attributesDeleted: "ATTRIBUTES_DELETED",
  403 + timeseriesUpdated: "TIMESERIES_UPDATED"
  404 + },
  405 + edgeAttributeKeys: {
  406 + active: "active",
  407 + lastConnectTime: "lastConnectTime",
  408 + lastDisconnectTime: "lastDisconnectTime",
  409 + queueStartTs: "queueStartTs"
  410 + },
392 411 importEntityColumnType: {
393 412 name: {
394 413 name: 'import.column-type.name',
... ...
... ... @@ -21,6 +21,21 @@
21 21 <md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"
22 22 ng-show="!isEdit && (edgeScope === 'customer' || edgeScope === 'tenant') && isAssignedToCustomer"
23 23 class="md-raised md-primary">{{ isPublic ? 'edge.make-private' : 'edge.unassign-from-customer' | translate }}</md-button>
  24 +<md-button ng-click="onManageEdgeAssets({event: $event})"
  25 + ng-show="!isEdit && edgeScope === 'tenant'"
  26 + class="md-raised md-primary">{{ 'edge.manage-edge-assets' | translate }}</md-button>
  27 +<md-button ng-click="onManageEdgeDevices({event: $event})"
  28 + ng-show="!isEdit && edgeScope === 'tenant'"
  29 + class="md-raised md-primary">{{ 'edge.manage-edge-devices' | translate }}</md-button>
  30 +<md-button ng-click="onManageEdgeEntityViews({event: $event})"
  31 + ng-show="!isEdit && edgeScope === 'tenant'"
  32 + class="md-raised md-primary">{{ 'edge.manage-edge-entity-views' | translate }}</md-button>
  33 +<md-button ng-click="onManageEdgeDashboards({event: $event})"
  34 + ng-show="!isEdit && edgeScope === 'tenant'"
  35 + class="md-raised md-primary">{{ 'edge.manage-edge-dashboards' | translate }}</md-button>
  36 +<md-button ng-click="onManageEdgeRuleChains({event: $event})"
  37 + ng-show="!isEdit && edgeScope === 'tenant'"
  38 + class="md-raised md-primary">{{ 'edge.manage-edge-rulechains' | translate }}</md-button>
24 39 <md-button ng-click="onDeleteEdge({event: $event})"
25 40 ng-show="!isEdit && edgeScope === 'tenant'"
26 41 class="md-raised md-primary">{{ 'edge.delete' | translate }}</md-button>
... ...
... ... @@ -129,6 +129,11 @@ export function EdgeController($rootScope, userService, edgeService, customerSer
129 129 vm.assignToCustomer = assignToCustomer;
130 130 vm.makePublic = makePublic;
131 131 vm.unassignFromCustomer = unassignFromCustomer;
  132 + vm.openEdgeAssets = openEdgeAssets;
  133 + vm.openEdgeDevices = openEdgeDevices;
  134 + vm.openEdgeEntityViews = openEdgeEntityViews;
  135 + vm.openEdgeDashboards = openEdgeDashboards;
  136 + vm.openEdgeRuleChains = openEdgeRuleChains;
132 137
133 138 initController();
134 139
... ...
... ... @@ -96,6 +96,11 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD
96 96 onAssignToCustomer: '&',
97 97 onMakePublic: '&',
98 98 onUnassignFromCustomer: '&',
  99 + onManageEdgeAssets: '&',
  100 + onManageEdgeDevices: '&',
  101 + onManageEdgeEntityViews: '&',
  102 + onManageEdgeDashboards: '&',
  103 + onManageEdgeRuleChains: '&',
99 104 onDeleteEdge: '&'
100 105 }
101 106 };
... ...
... ... @@ -29,6 +29,11 @@
29 29 on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
30 30 on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
31 31 on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
  32 + on-manage-edge-assets="vm.openEdgeAssets(event, vm.grid.detailsConfig.currentItem)"
  33 + on-manage-edge-devices="vm.openEdgeDevices(event, vm.grid.detailsConfig.currentItem)"
  34 + on-manage-edge-entity-views="vm.openEdgeEntityViews(event, vm.grid.detailsConfig.currentItem)"
  35 + on-manage-edge-dashboards="vm.openEdgeDashboards(event, vm.grid.detailsConfig.currentItem)"
  36 + on-manage-edge-rule-chains="vm.openEdgeRuleChains(event, vm.grid.detailsConfig.currentItem)"
32 37 on-delete-edge="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-edge>
33 38 </md-tab>
34 39 <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
... ...
... ... @@ -17,6 +17,7 @@
17 17 -->
18 18 <div translate class="tb-cell" flex="20">event.event-time</div>
19 19 <div translate class="tb-cell" flex="20">event.event-type</div>
20   -<div translate class="tb-cell" flex="20">edge.event-action</div>
21   -<div translate class="tb-cell" flex="30">edge.entity-id</div>
22   -<div translate class="tb-cell" flex="20">edge.entity-info</div>
  20 +<div translate class="tb-cell" flex="40">edge.event-action</div>
  21 +<div translate class="tb-cell" flex="20">edge.entity-id</div>
  22 +<div translate class="tb-cell" flex="15">edge.status</div>
  23 +<div translate class="tb-cell" flex="10">edge.entity-info</div>
... ...
... ... @@ -15,11 +15,12 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div class="tb-cell" flex="20">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
19   -<div class="tb-cell" flex="20">{{event.edgeEventType}}</div>
20   -<div class="tb-cell" flex="20">{{event.edgeEventAction}}</div>
21   -<div class="tb-cell" flex="30">{{event.entityId}}</div>
22   -<div class="tb-cell" flex="20">
  18 +<div class="tb-cell" flex="20">{{ event.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div>
  19 +<div class="tb-cell" flex="20">{{ event.edgeEventType }}</div>
  20 +<div class="tb-cell" flex="40">{{ event.edgeEventAction }}</div>
  21 +<div class="tb-cell" flex="20">{{ event.entityId }}</div>
  22 +<div class="tb-cell" flex="15" ng-style="isPending ? {'color': 'rgba(0, 0, 0, .38)'} : {'color': '#000'}">{{ updateStatus(event.createdTime) | translate }}</div>
  23 +<div class="tb-cell" flex="10">
23 24 <md-button class="md-icon-button md-primary"
24 25 ng-click="showEdgeEntityContent($event, 'edge.entity-info', 'JSON')"
25 26 aria-label="{{ 'action.view' | translate }}">
... ... @@ -33,3 +34,4 @@
33 34 </md-button>
34 35 </div>
35 36
  37 +
... ...
... ... @@ -102,37 +102,45 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
102 102 switch(scope.event.edgeEventType) {
103 103 case types.edgeEventType.relation:
104 104 content = angular.toJson(scope.event.entityBody);
  105 + showDialog();
105 106 break;
106 107 case types.edgeEventType.ruleChainMetaData:
107   - content = ruleChainService.getRuleChainMetaData(scope.event.entityId, {}).then(
  108 + content = ruleChainService.getRuleChainMetaData(scope.event.entityId, {ignoreErrors: true}).then(
108 109 function success(info) {
  110 + showDialog();
109 111 return angular.toJson(info);
110 112 }, function fail() {
111   - toast.showError($translate.instant('edge.load-entity-error'));
  113 + showError();
112 114 });
113 115 break;
114 116 default:
115   - content = entityService.getEntity(scope.event.edgeEventType, scope.event.entityId, {}).then(
  117 + content = entityService.getEntity(scope.event.edgeEventType, scope.event.entityId, {ignoreErrors: true}).then(
116 118 function success(info) {
  119 + showDialog();
117 120 return angular.toJson(info);
118 121 }, function fail() {
119   - toast.showError($translate.instant('edge.load-entity-error'));
  122 + showError();
120 123 });
121 124 break;
122 125 }
123   - $mdDialog.show({
124   - controller: 'EventContentDialogController',
125   - controllerAs: 'vm',
126   - templateUrl: eventErrorDialogTemplate,
127   - locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback},
128   - parent: angular.element($document[0].body),
129   - fullscreen: true,
130   - targetEvent: $event,
131   - multiple: true,
132   - onShowing: function(scope, element) {
133   - onShowingCallback.onShowing(scope, element);
134   - }
135   - });
  126 + function showDialog() {
  127 + $mdDialog.show({
  128 + controller: 'EventContentDialogController',
  129 + controllerAs: 'vm',
  130 + templateUrl: eventErrorDialogTemplate,
  131 + locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback},
  132 + parent: angular.element($document[0].body),
  133 + fullscreen: true,
  134 + targetEvent: $event,
  135 + multiple: true,
  136 + onShowing: function(scope, element) {
  137 + onShowingCallback.onShowing(scope, element);
  138 + }
  139 + });
  140 + }
  141 + function showError() {
  142 + toast.showError($translate.instant('edge.load-entity-error'));
  143 + }
136 144 }
137 145
138 146 scope.checkTooltip = function($event) {
... ... @@ -144,6 +152,20 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
144 152 }
145 153
146 154 $compile(element.contents())(scope);
  155 +
  156 + scope.updateStatus = function(eventCreatedTime) {
  157 + if (scope.queueStartTs) {
  158 + var status;
  159 + if (eventCreatedTime < scope.queueStartTs) {
  160 + status = $translate.instant('edge.success');
  161 + scope.isPending = false;
  162 + } else {
  163 + status = $translate.instant('edge.failed');
  164 + scope.isPending = true;
  165 + }
  166 + return status;
  167 + }
  168 + }
147 169 }
148 170
149 171 return {
... ...
... ... @@ -22,7 +22,8 @@ import eventTableTemplate from './event-table.tpl.html';
22 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 24 /*@ngInject*/
25   -export default function EventTableDirective($compile, $templateCache, $rootScope, types, eventService, edgeService) {
  25 +export default function EventTableDirective($compile, $templateCache, $rootScope, types,
  26 + eventService, edgeService, attributeService) {
26 27
27 28 var linker = function (scope, element, attrs) {
28 29
... ... @@ -30,11 +31,16 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
30 31
31 32 element.html(template);
32 33
  34 + scope.eventTypeScope = angular.copy(types.eventType);
  35 + if (scope.entityType !== types.entityType.edge) {
  36 + delete scope.eventTypeScope.edgeEvent;
  37 + }
  38 +
33 39 if (attrs.disabledEventTypes) {
34 40 var disabledEventTypes = attrs.disabledEventTypes.split(',');
35 41 scope.eventTypes = {};
36   - for (var type in types.eventType) {
37   - var eventType = types.eventType[type];
  42 + for (var type in scope.eventTypeScope) {
  43 + var eventType = scope.eventTypeScope[type];
38 44 var enabled = true;
39 45 for (var i=0;i<disabledEventTypes.length;i++) {
40 46 if (eventType.value === disabledEventTypes[i]) {
... ... @@ -47,7 +53,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
47 53 }
48 54 }
49 55 } else {
50   - scope.eventTypes = angular.copy(types.eventType);
  56 + scope.eventTypes = angular.copy(scope.eventTypeScope);
51 57 }
52 58
53 59 if (attrs.debugEventTypes) {
... ... @@ -106,6 +112,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
106 112 scope.eventType, scope.tenantId, scope.events.nextPageLink);
107 113 } else {
108 114 promise = edgeService.getEdgeEvents(scope.entityId, scope.events.nextPageLink);
  115 + scope.loadEdgeInfo();
109 116 }
110 117 if (promise) {
111 118 scope.events.pending = true;
... ... @@ -135,6 +142,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
135 142
136 143 scope.$watch("entityId", function(newVal, prevVal) {
137 144 if (newVal && !angular.equals(newVal, prevVal)) {
  145 + scope.loadEdgeInfo();
138 146 scope.resetFilter();
139 147 scope.reload();
140 148 }
... ... @@ -212,6 +220,53 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
212 220 return false;
213 221 }
214 222
  223 + scope.subscriptionId = null;
  224 + scope.queueStartTs;
  225 +
  226 + scope.loadEdgeInfo = function() {
  227 + attributeService.getEntityAttributesValues(scope.entityType, scope.entityId, types.attributesScope.server.value,
  228 + types.edgeAttributeKeys.queueStartTs, {})
  229 + .then(function success(attributes) {
  230 + scope.onUpdate(attributes);
  231 + });
  232 +
  233 + scope.checkSubscription();
  234 +
  235 + attributeService.getEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value, {order: '', limit: 1, page: 1, search: ''},
  236 + function (attributes) {
  237 + if (attributes && attributes.data) {
  238 + scope.onUpdate(attributes.data);
  239 + }
  240 + });
  241 + }
  242 +
  243 + scope.onUpdate = function(attributes) {
  244 + let edge = attributes.reduce(function (map, attribute) {
  245 + map[attribute.key] = attribute;
  246 + return map;
  247 + }, {});
  248 + if (edge.queueStartTs) {
  249 + scope.queueStartTs = edge.queueStartTs.lastUpdateTs;
  250 + }
  251 + }
  252 +
  253 + scope.checkSubscription = function() {
  254 + var newSubscriptionId = null;
  255 + if (scope.entityId && scope.entityType && types.attributesScope.server.value) {
  256 + newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value);
  257 + }
  258 + if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) {
  259 + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);
  260 + }
  261 + scope.subscriptionId = newSubscriptionId;
  262 + }
  263 +
  264 + scope.$on('$destroy', function () {
  265 + if (scope.subscriptionId) {
  266 + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);
  267 + }
  268 + });
  269 +
215 270 scope.reload();
216 271
217 272 $compile(element.contents())(scope);
... ...
... ... @@ -821,7 +821,7 @@
821 821 "make-private-edge-text": "After the confirmation the edge and all its data will be made private and won't be accessible by others.",
822 822 "import": "Import edge",
823 823 "label": "Label",
824   - "load-entity-error": "Could not load entity info",
  824 + "load-entity-error": "Entity not found. Failed to load info",
825 825 "assign-new-edge": "Assign new edge",
826 826 "manage-edge-dashboards": "Manage edge dashboards",
827 827 "unassign-from-edge": "Unassign from edge",
... ... @@ -843,7 +843,10 @@
843 843 "entity-views": "Edge entity views",
844 844 "set-root-rule-chain-text": "Please select root rule chain for edge(s)",
845 845 "set-root-rule-chain-to-edges": "Set root rule chain for Edge(s)",
846   - "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }"
  846 + "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }",
  847 + "status": "Received by edge",
  848 + "success": "Deployed",
  849 + "failed": "Pending"
847 850 },
848 851 "error": {
849 852 "unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
... ...