From a0e74d72054393cc28bb34cd6ecca0b896798715 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Wed, 6 Jan 2021 09:38:31 +0200 Subject: [PATCH] Edge Downlinks new tab implementation 2 --- ...dge-downlinks-content-dialog.controller.js | 92 +++++++ .../edge-downlinks-header.directive.js | 54 ++++ .../downlinks/edge-downlinks-row.directive.js | 140 +++++++++++ .../edge-downlinks-table.directive.js | 234 ++++++++++++++++++ .../downlinks/edge-downlinks-table.tpl.html | 46 ++++ .../downlinks/event-content-dialog.tpl.html | 42 ++++ .../event-header-edge-event.tpl.html | 23 ++ .../downlinks/event-row-edge-event.tpl.html | 37 +++ ui/src/app/edge/downlinks/event.scss | 97 ++++++++ ui/src/app/edge/edges.tpl.html | 11 +- ui/src/app/edge/index.js | 8 + .../app/event/edge-downlinks-table.tpl.html | 2 +- ui/src/app/event/index.js | 2 +- 13 files changed, 784 insertions(+), 4 deletions(-) create mode 100644 ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js create mode 100644 ui/src/app/edge/downlinks/edge-downlinks-header.directive.js create mode 100644 ui/src/app/edge/downlinks/edge-downlinks-row.directive.js create mode 100644 ui/src/app/edge/downlinks/edge-downlinks-table.directive.js create mode 100644 ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html create mode 100644 ui/src/app/edge/downlinks/event-content-dialog.tpl.html create mode 100644 ui/src/app/edge/downlinks/event-header-edge-event.tpl.html create mode 100644 ui/src/app/edge/downlinks/event-row-edge-event.tpl.html create mode 100644 ui/src/app/edge/downlinks/event.scss diff --git a/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js b/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js new file mode 100644 index 0000000000..7be69ea17f --- /dev/null +++ b/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js @@ -0,0 +1,92 @@ +/* + * 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 $ from 'jquery'; +import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; +import 'brace/mode/java'; +import 'brace/theme/github'; +import beautify from 'js-beautify'; + +/* eslint-disable angular/angularelement */ + +const js_beautify = beautify.js; + +/*@ngInject*/ +export default function EdgeDownlinksContentDialogController($mdDialog, types, content, contentType, title, showingCallback) { + + var vm = this; + + showingCallback.onShowing = function(scope, element) { + updateEditorSize(element); + } + + vm.content = content; + vm.title = title; + + var mode; + if (contentType) { + mode = types.contentType[contentType].code; + if (contentType == types.contentType.JSON.value && vm.content) { + vm.content = js_beautify(vm.content, {indent_size: 4}); + } + } else { + mode = 'java'; + } + + vm.contentOptions = { + useWrapMode: false, + mode: mode, + showGutter: false, + showPrintMargin: false, + theme: 'github', + advanced: { + enableSnippets: false, + enableBasicAutocompletion: false, + enableLiveAutocompletion: false + }, + onLoad: function (_ace) { + vm.editor = _ace; + } + }; + + function updateEditorSize(element) { + var newHeight = 400; + var newWidth = 600; + if (vm.content && vm.content.length > 0) { + var lines = vm.content.split('\n'); + newHeight = 16 * lines.length + 16; + var maxLineLength = 0; + for (var i = 0; i < lines.length; i++) { + var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); + var lineLength = line.length; + maxLineLength = Math.max(maxLineLength, lineLength); + } + newWidth = 8 * maxLineLength + 16; + } + $('#tb-event-content', element).height(newHeight.toString() + "px") + .width(newWidth.toString() + "px"); + vm.editor.resize(); + } + + vm.close = close; + + function close () { + $mdDialog.hide(); + } + +} + +/* eslint-enable angular/angularelement */ diff --git a/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js new file mode 100644 index 0000000000..fd14df88d5 --- /dev/null +++ b/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js @@ -0,0 +1,54 @@ +/* + * 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. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import edgeDownlinksHeaderTemplate from './event-header-edge-event.tpl.html' + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function EventHeaderDirective2($compile, $templateCache, types) { + + var linker = function (scope, element, attrs) { + + var getTemplate = function(eventType) { + var template = ''; + switch(eventType) { + case types.edgeDownlinks.value: + template = edgeDownlinksHeaderTemplate; + break; + } + return $templateCache.get(template); + } + + scope.loadTemplate = function() { + element.html(getTemplate(attrs.eventType)); + $compile(element.contents())(scope); + } + + attrs.$observe('eventType', function() { + scope.loadTemplate(); + }); + + } + + return { + restrict: "A", + replace: false, + link: linker, + scope: false + }; +} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js new file mode 100644 index 0000000000..20c2eab89c --- /dev/null +++ b/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js @@ -0,0 +1,140 @@ +/* + * 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. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import eventErrorDialogTemplate from './event-content-dialog.tpl.html'; + +import edgeDownlinlsRowTemplate from './event-row-edge-event.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function EventRowDirective2($compile, $templateCache, $mdDialog, $document, $translate, + types, utils, toast, entityService, ruleChainService) { + + var linker = function (scope, element, attrs) { + + var getTemplate = function(eventType) { + var template = ''; + switch(eventType) { + case types.edgeDownlinks.value: + template = edgeDownlinlsRowTemplate; + break; + } + return $templateCache.get(template); + } + + scope.loadTemplate = function() { + element.html(getTemplate(attrs.eventType)); + $compile(element.contents())(scope); + } + + attrs.$observe('eventType', function() { + scope.loadTemplate(); + }); + + scope.types = types; + + scope.event = attrs.event; + + scope.showEdgeEntityContent = function($event, title, contentType) { + var onShowingCallback = { + onShowing: function(){} + } + if (!contentType) { + contentType = null; + } + var content = ''; + switch(scope.event.type) { + case types.edgeEventType.relation: + content = angular.toJson(scope.event.body); + showDialog(); + break; + case types.edgeEventType.ruleChainMetaData: + content = ruleChainService.getRuleChainMetaData(scope.event.entityId, {ignoreErrors: true}).then( + function success(info) { + showDialog(); + return angular.toJson(info); + }, function fail() { + showError(); + }); + break; + default: + content = entityService.getEntity(scope.event.type, scope.event.entityId, {ignoreErrors: true}).then( + function success(info) { + showDialog(); + return angular.toJson(info); + }, function fail() { + showError(); + }); + break; + } + function showDialog() { + $mdDialog.show({ + controller: 'EventContentDialogController2', + controllerAs: 'vm', + templateUrl: eventErrorDialogTemplate, + locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, + parent: angular.element($document[0].body), + fullscreen: true, + targetEvent: $event, + multiple: true, + onShowing: function(scope, element) { + onShowingCallback.onShowing(scope, element); + } + }); + } + function showError() { + toast.showError($translate.instant('edge.load-entity-error')); + } + } + + scope.checkEdgeEventType = function (type) { + return !(type === types.edgeEventType.widgetType || + type === types.edgeEventType.adminSettings || + type === types.edgeEventType.widgetsBundle ); + } + + scope.checkTooltip = function($event) { + var el = $event.target; + var $el = angular.element(el); + if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ + $el.attr('title', $el.text()); + } + } + + $compile(element.contents())(scope); + + scope.updateStatus = function(eventCreatedTime) { + var status; + if (eventCreatedTime < scope.queueStartTs) { + status = $translate.instant(types.edgeEventStatus.DEPLOYED.name); + scope.statusColor = types.edgeEventStatus.DEPLOYED.color; + } else { + status = $translate.instant(types.edgeEventStatus.PENDING.name); + scope.statusColor = types.edgeEventStatus.PENDING.color; + } + return status; + } + } + + return { + restrict: "A", + replace: false, + link: linker, + scope: false + }; +} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js new file mode 100644 index 0000000000..46f194b68b --- /dev/null +++ b/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js @@ -0,0 +1,234 @@ +/* + * 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 EdgeDownlinksDirective2($compile, $templateCache, $rootScope, $translate, types, + eventService, edgeService, attributeService) { + + var linker = function (scope, element) { + + var template = $templateCache.get(edgeDownlinksTableTemplate); + + element.html(template); + + scope.eventType = types.edgeDownlinks.value; + + 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("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/edge/downlinks/edge-downlinks-table.tpl.html b/ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html new file mode 100644 index 0000000000..4bff7afa4b --- /dev/null +++ b/ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html @@ -0,0 +1,46 @@ + + +
+ + + refresh + + {{ 'action.refresh' | translate }} + + +
+ + + + + + edge.no-downlinks-prompt + + + + + + + + +
diff --git a/ui/src/app/edge/downlinks/event-content-dialog.tpl.html b/ui/src/app/edge/downlinks/event-content-dialog.tpl.html new file mode 100644 index 0000000000..3959da43f4 --- /dev/null +++ b/ui/src/app/edge/downlinks/event-content-dialog.tpl.html @@ -0,0 +1,42 @@ + + + +
+

{{ vm.title }}

+ + + + +
+
+ +
+
+
+
+
+ + + {{ 'action.close' | + translate }} + + +
diff --git a/ui/src/app/edge/downlinks/event-header-edge-event.tpl.html b/ui/src/app/edge/downlinks/event-header-edge-event.tpl.html new file mode 100644 index 0000000000..3de281ddc7 --- /dev/null +++ b/ui/src/app/edge/downlinks/event-header-edge-event.tpl.html @@ -0,0 +1,23 @@ + +
event.event-time
+
event.event-type
+
edge.event-action
+
edge.entity-id
+
edge.status
+
edge.entity-info
diff --git a/ui/src/app/edge/downlinks/event-row-edge-event.tpl.html b/ui/src/app/edge/downlinks/event-row-edge-event.tpl.html new file mode 100644 index 0000000000..a842b5d787 --- /dev/null +++ b/ui/src/app/edge/downlinks/event-row-edge-event.tpl.html @@ -0,0 +1,37 @@ + +
{{ event.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}
+
{{ event.edgeEventTypeText }}
+
{{ event.edgeEventActionText }}
+
{{ event.entityId }}
+
{{ updateStatus(event.createdTime) }}
+
+ + + {{ 'action.view' | translate }} + + + more_horiz + + +
+ + diff --git a/ui/src/app/edge/downlinks/event.scss b/ui/src/app/edge/downlinks/event.scss new file mode 100644 index 0000000000..7e67d0aac7 --- /dev/null +++ b/ui/src/app/edge/downlinks/event.scss @@ -0,0 +1,97 @@ +/** + * 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-list.tb-event-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-edge-downlinks-table-2 { + @extend .tb-event-table; +} + +#tb-event-content { + width: 100%; + min-width: 400px; + height: 100%; + min-height: 50px; +} diff --git a/ui/src/app/edge/edges.tpl.html b/ui/src/app/edge/edges.tpl.html index 016634fea9..d1cb6182cd 100644 --- a/ui/src/app/edge/edges.tpl.html +++ b/ui/src/app/edge/edges.tpl.html @@ -65,11 +65,18 @@ default-event-type="{{vm.types.eventType.error.value}}"> - - + + + + + diff --git a/ui/src/app/edge/index.js b/ui/src/app/edge/index.js index ef9f34d601..56a8558dd1 100644 --- a/ui/src/app/edge/index.js +++ b/ui/src/app/edge/index.js @@ -25,6 +25,10 @@ import AssignEdgeToCustomerController from './assign-to-customer.controller'; import AddEdgesToCustomerController from './add-edges-to-customer.controller'; import SetRootRuleChainToEdgesController from './set-root-rule-chain-to-edges.controller'; import EdgeDirective from './edge.directive'; +import EdgeDownlinksContentDialogController from './downlinks/edge-downlinks-content-dialog.controller'; +import EdgeDownlinksHeaderDirective from './downlinks/edge-downlinks-header.directive'; +import EdgeDownlinksRowDirective from './downlinks/edge-downlinks-row.directive'; +import EdgeDownlinksDirective from "./downlinks/edge-downlinks-table.directive"; export default angular.module('thingsboard.edge', [ uiRouter, @@ -39,5 +43,9 @@ export default angular.module('thingsboard.edge', [ .controller('AssignEdgeToCustomerController', AssignEdgeToCustomerController) .controller('AddEdgesToCustomerController', AddEdgesToCustomerController) .controller('SetRootRuleChainToEdgesController', SetRootRuleChainToEdgesController) + .controller('EdgeDownlinksContentDialogController', EdgeDownlinksContentDialogController) .directive('tbEdge', EdgeDirective) + .directive('tbEdgeDownlinksHeader', EdgeDownlinksHeaderDirective) + .directive('tbEdgeDownlinksRow', EdgeDownlinksRowDirective) + .directive('tbEdgeDownlinksTable', EdgeDownlinksDirective) .name; diff --git a/ui/src/app/event/edge-downlinks-table.tpl.html b/ui/src/app/event/edge-downlinks-table.tpl.html index 2660cab1b3..d55e61457c 100644 --- a/ui/src/app/event/edge-downlinks-table.tpl.html +++ b/ui/src/app/event/edge-downlinks-table.tpl.html @@ -26,7 +26,7 @@ - +