diff --git a/ui/src/app/api/edge.service.js b/ui/src/app/api/edge.service.js index debd49069a..809b0b12af 100644 --- a/ui/src/app/api/edge.service.js +++ b/ui/src/app/api/edge.service.js @@ -35,7 +35,7 @@ function EdgeService($http, $q, customerService) { unassignEdgeFromCustomer: unassignEdgeFromCustomer, makeEdgePublic: makeEdgePublic, setRootRuleChain: setRootRuleChain, - getEdgeEvents: getEdgeEvents, + getEdgeDownlinks: getEdgeDownlinks, syncEdge: syncEdge, findMissingToRelatedRuleChains: findMissingToRelatedRuleChains }; @@ -276,7 +276,7 @@ function EdgeService($http, $q, customerService) { return deferred.promise; } - function getEdgeEvents(edgeId, pageLink) { + function getEdgeDownlinks(edgeId, pageLink) { var deferred = $q.defer(); var url = '/api/edge/' + edgeId + '/events' + '?limit=' + pageLink.limit; if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 7dbc10de6d..0bdb4fae71 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -661,10 +661,6 @@ export default angular.module('thingsboard.types', []) stats: { value: "STATS", name: "event.type-stats" - }, - edgeEvent: { - value: "EDGE_EVENT", - name: "event.type-edge-event" } }, debugEventType: { @@ -1197,6 +1193,14 @@ export default angular.module('thingsboard.types', []) "ADMIN_SETTINGS": { name: "permission.resource.display-type.ADMIN_SETTINGS" } + }, + edgeEvent: { + value: "EDGE_EVENT", + name: "edge.downlink" + }, + edgeDownlinks: { + value: "EDGE_DOWNLINKS", + name: "edge.downlinks" } } ).name; diff --git a/ui/src/app/edge/edges.tpl.html b/ui/src/app/edge/edges.tpl.html index 2600413255..016634fea9 100644 --- a/ui/src/app/edge/edges.tpl.html +++ b/ui/src/app/edge/edges.tpl.html @@ -65,6 +65,13 @@ default-event-type="{{vm.types.eventType.error.value}}"> + + + + scope.events.data.length) { + scope.theEvents.fetchMoreItems_(index); + return null; + } + var item = scope.events.data[index]; + if (item) { + item.indexNumber = index + 1; + } + return item; + }, + + getLength: function () { + if (scope.events.hasNext) { + return scope.events.data.length + scope.events.nextPageLink.limit; + } else { + return scope.events.data.length; + } + }, + + fetchMoreItems_: function () { + if (scope.events.hasNext && !scope.events.pending) { + if (scope.entityType && scope.entityId && scope.eventType && scope.tenantId) { + scope.loadEdgeInfo(); + scope.events.pending = true; + edgeService.getEdgeDownlinks(scope.entityId, scope.events.nextPageLink).then( + function success(events) { + scope.events.data = scope.events.data.concat(prepareEdgeEventData(events.data)); + scope.events.nextPageLink = events.nextPageLink; + scope.events.hasNext = events.hasNext; + if (scope.events.hasNext) { + scope.events.nextPageLink.limit = pageSize; + } + scope.events.pending = false; + }, + function fail() { + scope.events.hasNext = false; + scope.events.pending = false; + }); + } else { + scope.events.hasNext = false; + } + } + } + }; + + scope.$watch("entityId", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + scope.resetFilter(); + scope.reload(); + } + }); + + scope.$watch("eventType", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + scope.reload(); + } + }); + + scope.$watch("timewindow", function(newVal, prevVal) { + if (newVal && !angular.equals(newVal, prevVal)) { + scope.reload(); + } + }, true); + + scope.resetFilter = function() { + scope.timewindow = { + history: { + timewindowMs: 24 * 60 * 60 * 1000 // 1 day + } + }; + } + + scope.updateTimeWindowRange = function() { + 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; + } + } + + scope.reload = function() { + scope.topIndex = 0; + scope.selected = []; + scope.updateTimeWindowRange(); + scope.events = { + data: [], + nextPageLink: { + limit: pageSize, + startTime: startTime, + endTime: endTime + }, + hasNext: true, + pending: false + }; + scope.theEvents.getItemAtIndex(pageSize); + } + + scope.noData = function() { + return scope.events.data.length == 0 && !scope.events.hasNext; + } + + scope.hasData = function() { + return scope.events.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; + } + + scope.subscriptionId = null; + + scope.loadEdgeInfo = function() { + attributeService.getEntityAttributesValues( + scope.entityType, + scope.entityId, + types.attributesScope.server.value, + types.edgeAttributeKeys.queueStartTs, + null).then( + function success(attributes) { + attributes.length > 0 ? scope.onEdgeAttributesUpdate(attributes) : scope.queueStartTs = 0; + }); + scope.checkSubscription(); + } + + scope.onEdgeAttributesUpdate = function(attributes) { + let edgeAttributes = attributes.reduce(function (map, attribute) { + map[attribute.key] = attribute; + return map; + }, {}); + if (edgeAttributes.queueStartTs) { + scope.queueStartTs = edgeAttributes.queueStartTs.lastUpdateTs; + } + } + + scope.checkSubscription = function() { + var newSubscriptionId = null; + if (scope.entityId && scope.entityType && types.attributesScope.server.value) { + newSubscriptionId = + attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value); + } + if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); + } + scope.subscriptionId = newSubscriptionId; + } + + scope.$on('$destroy', function () { + if (scope.subscriptionId) { + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); + } + }); + + scope.reload(); + + $compile(element.contents())(scope); + } + function prepareEdgeEventData(data) { + + data.forEach( + edgeEvent => { + edgeEvent.edgeEventActionText = $translate.instant(types.edgeEventActionType[edgeEvent.action].name); + edgeEvent.edgeEventTypeText = $translate.instant(types.edgeEventTypeTranslations[edgeEvent.edgeId.entityType].name); + } + ); + return data; + } + + return { + restrict: "E", + link: linker, + scope: { + entityType: '=', + entityId: '=', + tenantId: '=' + } + }; +} diff --git a/ui/src/app/event/edge-downlinks-table.tpl.html b/ui/src/app/event/edge-downlinks-table.tpl.html new file mode 100644 index 0000000000..e0281a2cf0 --- /dev/null +++ b/ui/src/app/event/edge-downlinks-table.tpl.html @@ -0,0 +1,46 @@ + + +
+ + + refresh + + {{ 'action.refresh' | translate }} + + +
+ + + + + + event.no-events-prompt + + + + + + + + +
diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js index efe3dab21c..ac5e968745 100644 --- a/ui/src/app/event/event-header.directive.js +++ b/ui/src/app/event/event-header.directive.js @@ -46,7 +46,7 @@ export default function EventHeaderDirective($compile, $templateCache, types) { case types.debugEventType.debugRuleChain.value: template = eventHeaderDebugRuleNodeTemplate; break; - case types.eventType.edgeEvent.value: + case types.edgeDownlinks.value: template = eventHeaderEdgeEventTemplate; break; } diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js index 77718a5968..b4d20fd72a 100644 --- a/ui/src/app/event/event-row.directive.js +++ b/ui/src/app/event/event-row.directive.js @@ -49,7 +49,7 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ case types.debugEventType.debugRuleChain.value: template = eventRowDebugRuleNodeTemplate; break; - case types.eventType.edgeEvent.value: + case types.edgeDownlinks.value: template = eventRowEdgeEventTemplate; break; } diff --git a/ui/src/app/event/event-table.directive.js b/ui/src/app/event/event-table.directive.js index ba41da8c34..7537c808f3 100644 --- a/ui/src/app/event/event-table.directive.js +++ b/ui/src/app/event/event-table.directive.js @@ -22,8 +22,7 @@ import eventTableTemplate from './event-table.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function EventTableDirective($compile, $templateCache, $rootScope, $translate, types, - eventService, edgeService, attributeService) { +export default function EventTableDirective($compile, $templateCache, $rootScope, types, eventService) { var linker = function (scope, element, attrs) { @@ -31,16 +30,11 @@ export default function EventTableDirective($compile, $templateCache, $rootScope element.html(template); - scope.eventTypeScope = angular.copy(types.eventType); - if (scope.entityType !== types.entityType.edge) { - delete scope.eventTypeScope.edgeEvent; - } - if (attrs.disabledEventTypes) { var disabledEventTypes = attrs.disabledEventTypes.split(','); scope.eventTypes = {}; - for (var type in scope.eventTypeScope) { - var eventType = scope.eventTypeScope[type]; + for (var type in types.eventType) { + var eventType = types.eventType[type]; var enabled = true; for (var i=0;i 0 ? scope.onEdgeAttributesUpdate(attributes) : scope.queueStartTs = 0; - }); - scope.checkSubscription(); - } - - scope.onEdgeAttributesUpdate = function(attributes) { - let edgeAttributes = attributes.reduce(function (map, attribute) { - map[attribute.key] = attribute; - return map; - }, {}); - if (edgeAttributes.queueStartTs) { - scope.queueStartTs = edgeAttributes.queueStartTs.lastUpdateTs; - } - } - - scope.checkSubscription = function() { - var newSubscriptionId = null; - if (scope.entityId && scope.entityType && types.attributesScope.server.value) { - newSubscriptionId = - attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value); - } - if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); - } - scope.subscriptionId = newSubscriptionId; - } - - scope.$on('$destroy', function () { - if (scope.subscriptionId) { - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); - } - }); - scope.reload(); $compile(element.contents())(scope); } - - function prepareEdgeEventData(data) { - data.forEach( - edgeEvent => { - edgeEvent.edgeEventActionText = $translate.instant(types.edgeEventActionType[edgeEvent.action].name); - edgeEvent.edgeEventTypeText = $translate.instant(types.edgeEventTypeTranslations[edgeEvent.edgeId.entityType].name); - } - ); - return data; - } - return { restrict: "E", link: linker, diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss index f1b5eb0d46..ffdb4217bd 100644 --- a/ui/src/app/event/event.scss +++ b/ui/src/app/event/event.scss @@ -85,6 +85,78 @@ md-list.tb-event-table { } } +md-list.tb-edge-downlinks-table { + padding: 0; + + md-list-item { + padding: 0; + } + + .tb-row { + height: 48px; + padding: 0; + overflow: hidden; + + .tb-cell { + text-overflow: ellipsis; + + &.tb-scroll { + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + } + + &.tb-nowrap { + white-space: nowrap; + } + } + } + + .tb-row:hover { + background-color: #eee; + } + + .tb-header:hover { + background: none; + } + + .tb-header { + .tb-cell { + font-size: 12px; + font-weight: 700; + color: rgba(0, 0, 0, .54); + white-space: nowrap; + background: none; + } + } + + .tb-cell { + &:first-child { + padding-left: 14px; + } + + &:last-child { + padding-right: 14px; + } + padding: 0 6px; + margin: auto 0; + overflow: hidden; + font-size: 13px; + color: rgba(0, 0, 0, .87); + text-align: left; + vertical-align: middle; + + .md-button { + padding: 0; + margin: 0; + } + } + + .tb-cell.tb-number { + text-align: right; + } +} + #tb-event-content { width: 100%; min-width: 400px; diff --git a/ui/src/app/event/index.js b/ui/src/app/event/index.js index 69227eef8b..370f31a17b 100644 --- a/ui/src/app/event/index.js +++ b/ui/src/app/event/index.js @@ -19,6 +19,7 @@ import EventContentDialogController from './event-content-dialog.controller'; import EventHeaderDirective from './event-header.directive'; import EventRowDirective from './event-row.directive'; import EventTableDirective from './event-table.directive'; +import EdgeDownlinksDirective from "./edge-downlinks-table.directive"; export default angular.module('thingsboard.event', [ thingsboardApiEvent @@ -27,4 +28,5 @@ export default angular.module('thingsboard.event', [ .directive('tbEventHeader', EventHeaderDirective) .directive('tbEventRow', EventRowDirective) .directive('tbEventTable', EventTableDirective) + .directive('tbEdgeDownlinksTable', EdgeDownlinksDirective) .name; diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 56f45586c3..781ceced3f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -856,7 +856,9 @@ "license-key-hint": "To obtain your license please navigate to the pricing page and select the best license option for your case.", "cloud-endpoint-hint": "Edge requires HTTP(s) access to Cloud (ThingsBoard CE/PE) to verify the license key. Please specify Cloud URL that Edge is able to connect to.", "missing-related-rule-chains-title": "Edge has missing related rule chain(s)", - "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}" + "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}", + "downlinks": "Downlinks", + "no-downlinks-prompt": "No downlinks found" }, "error": { "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", @@ -1097,7 +1099,6 @@ "type-debug-rule-chain": "Debug", "no-events-prompt": "No events found", "error": "Error", - "type-edge-event": "Downlink", "alarm": "Alarm", "event-time": "Event time", "server": "Server",