Browse Source

Edge Downlinks as tab

pull/3957/head
Artem Babak 6 years ago
parent
commit
0a5796c9fd
  1. 4
      ui/src/app/api/edge.service.js
  2. 12
      ui/src/app/common/types.constant.js
  3. 7
      ui/src/app/edge/edges.tpl.html
  4. 272
      ui/src/app/event/edge-downlinks-table.directive.js
  5. 46
      ui/src/app/event/edge-downlinks-table.tpl.html
  6. 2
      ui/src/app/event/event-header.directive.js
  7. 2
      ui/src/app/event/event-row.directive.js
  8. 84
      ui/src/app/event/event-table.directive.js
  9. 72
      ui/src/app/event/event.scss
  10. 2
      ui/src/app/event/index.js
  11. 5
      ui/src/app/locale/locale.constant-en_US.json

4
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) {

12
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;

7
ui/src/app/edge/edges.tpl.html

@ -65,6 +65,13 @@
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'edge.downlinks' | translate }}">
<tb-edge-downlinks-table flex entity-type="vm.types.entityType.edge"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.edgeDownlinks.value}}">
</tb-edge-downlinks-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
readonly="!('edge' | hasGenericPermission:'write')"

272
ui/src/app/event/edge-downlinks-table.directive.js

@ -0,0 +1,272 @@
/*
* Copyright © 2016-2020 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 './event.scss';
/* eslint-disable import/no-unresolved, import/default */
import edgeDownlinksTableTemplate from './edge-downlinks-table.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EdgeDownlinksDirective($compile, $templateCache, $rootScope, $translate, types,
eventService, edgeService, attributeService) {
var linker = function (scope, element, attrs) {
var template = $templateCache.get(edgeDownlinksTableTemplate);
element.html(template);
if (attrs.disabledEventTypes) {
var disabledEventTypes = attrs.disabledEventTypes.split(',');
scope.eventTypes = {};
for (var type in types.eventType) {
var eventType = types.eventType[type];
var enabled = true;
for (var i=0;i<disabledEventTypes.length;i++) {
if (eventType.value === disabledEventTypes[i]) {
enabled = false;
break;
}
}
if (enabled) {
scope.eventTypes[type] = eventType;
}
}
} else {
scope.eventTypes = angular.copy(types.eventType);
}
if (attrs.debugEventTypes) {
var debugEventTypes = attrs.debugEventTypes.split(',');
for (i=0;i<debugEventTypes.length;i++) {
for (type in types.debugEventType) {
eventType = types.debugEventType[type];
if (eventType.value === debugEventTypes[i]) {
scope.eventTypes[type] = eventType;
}
}
}
}
scope.eventType = attrs.defaultEventType;
var pageSize = 20;
var startTime = 0;
var endTime = 0;
scope.timewindow = {
history: {
timewindowMs: 24 * 60 * 60 * 1000 // 1 day
}
}
scope.topIndex = 0;
scope.theEvents = {
getItemAtIndex: function (index) {
if (index > 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: '='
}
};
}

46
ui/src/app/event/edge-downlinks-table.tpl.html

@ -0,0 +1,46 @@
<!--
Copyright © 2016-2020 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">
<tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
<md-button ng-disabled="$root.loading"
class="md-icon-button" ng-click="reload()">
<md-icon>refresh</md-icon>
<md-tooltip md-direction="top">
{{ 'action.refresh' | translate }}
</md-tooltip>
</md-button>
</section>
<md-list flex layout="column" class="md-whiteframe-z1 tb-edge-downlinks-table">
<md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-event-header event-type="{{eventType}}">
</md-list>
<md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"
ng-show="$root.loading"></md-progress-linear>
<md-divider></md-divider>
<span translate layout-align="center center"
style="margin-top: 25px;"
class="tb-prompt" ng-show="noData()">event.no-events-prompt</span>
<md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
<md-list-item md-virtual-repeat="event in theEvents" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
<md-list class="tb-row" flex layout="row" layout-align="start center" tb-event-row event-type="{{eventType}}" event="{{event}}">
</md-list>
<md-divider flex></md-divider>
</md-list-item>
</md-virtual-repeat-container>
</md-list>
</md-content>

2
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;
}

2
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;
}

84
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<disabledEventTypes.length;i++) {
if (eventType.value === disabledEventTypes[i]) {
@ -53,7 +47,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
}
}
} else {
scope.eventTypes = angular.copy(scope.eventTypeScope);
scope.eventTypes = angular.copy(types.eventType);
}
if (attrs.debugEventTypes) {
@ -106,23 +100,13 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
fetchMoreItems_: function () {
if (scope.events.hasNext && !scope.events.pending) {
if (scope.entityType && scope.entityId && scope.eventType && scope.tenantId) {
var promise = '';
if (scope.eventType !== types.eventType.edgeEvent.value) {
promise = eventService.getEvents(scope.entityType, scope.entityId,
scope.eventType, scope.tenantId, scope.events.nextPageLink);
} else {
promise = edgeService.getEdgeEvents(scope.entityId, scope.events.nextPageLink);
scope.loadEdgeInfo();
}
var promise = eventService.getEvents(scope.entityType, scope.entityId,
scope.eventType, scope.tenantId, scope.events.nextPageLink);
if (promise) {
scope.events.pending = true;
promise.then(
function success(events) {
if (scope.eventType === types.eventType.edgeEvent.value) {
scope.events.data = scope.events.data.concat(prepareEdgeEventData(events.data));
} else {
scope.events.data = scope.events.data.concat(events.data);
}
scope.events.data = scope.events.data.concat(events.data);
scope.events.nextPageLink = events.nextPageLink;
scope.events.hasNext = events.hasNext;
if (scope.events.hasNext) {
@ -223,64 +207,10 @@ export default function EventTableDirective($compile, $templateCache, $rootScope
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,

72
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;

2
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;

5
ui/src/app/locale/locale.constant-en_US.json

@ -856,7 +856,9 @@
"license-key-hint": "To obtain your license please navigate to the <a href='https://thingsboard.io/pricing/?active=thingsboard-edge' target='_blank'>pricing page</a> 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. <br><br> List of missing rule chain(s): <br> {{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. <br><br> List of missing rule chain(s): <br> {{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",

Loading…
Cancel
Save