UI: Improve Rule Chain Editor

This commit is contained in:
Igor Kulikov 2018-03-30 21:24:58 +03:00
parent 39f682ce9f
commit dc47727cf5
11 changed files with 133 additions and 39 deletions

View File

@ -69,14 +69,18 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration())); && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode; this.ruleNode = newRuleNode;
if (restartRequired) { if (restartRequired) {
if (tbNode != null) {
tbNode.destroy(); tbNode.destroy();
}
start(context); start(context);
} }
} }
@Override @Override
public void stop(ActorContext context) throws Exception { public void stop(ActorContext context) throws Exception {
if (tbNode != null) {
tbNode.destroy(); tbNode.destroy();
}
context.stop(self); context.stop(self);
} }

View File

@ -15,13 +15,13 @@
limitations under the License. limitations under the License.
--> -->
<div hide-xs hide-sm translate class="tb-cell" flex="30">event.event-time</div> <div hide-xs hide-sm translate class="tb-cell" flex="25">event.event-time</div>
<div translate class="tb-cell" flex="20">event.server</div> <div translate class="tb-cell" flex="20">event.server</div>
<div translate class="tb-cell" flex="20">event.type</div> <div translate class="tb-cell" flex="10">event.type</div>
<div translate class="tb-cell" flex="20">event.entity</div> <div translate class="tb-cell" flex="15">event.entity</div>
<div translate class="tb-cell" flex="20">event.message-id</div> <div translate class="tb-cell" flex="20">event.message-id</div>
<div translate class="tb-cell" flex="20">event.message-type</div> <div translate class="tb-cell" flex="20">event.message-type</div>
<div translate class="tb-cell" flex="20">event.data-type</div> <div translate class="tb-cell" flex="15">event.data-type</div>
<div translate class="tb-cell" flex="20">event.data</div> <div translate class="tb-cell" flex="10">event.data</div>
<div translate class="tb-cell" flex="20">event.metadata</div> <div translate class="tb-cell" flex="10">event.metadata</div>
<div translate class="tb-cell" flex="20">event.error</div> <div translate class="tb-cell" flex="10">event.error</div>

View File

@ -15,14 +15,14 @@
limitations under the License. limitations under the License.
--> -->
<div hide-xs hide-sm class="tb-cell" flex="30">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div> <div hide-xs hide-sm class="tb-cell" flex="25">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div>
<div class="tb-cell" flex="20">{{event.body.server}}</div> <div class="tb-cell" flex="20">{{event.body.server}}</div>
<div class="tb-cell" flex="20">{{event.body.type}}</div> <div class="tb-cell" flex="10">{{event.body.type}}</div>
<div class="tb-cell" flex="20">{{event.body.entityName}}</div> <div class="tb-cell" flex="15">{{event.body.entityName}}</div>
<div class="tb-cell" flex="20">{{event.body.msgId}}</div> <div class="tb-cell tb-nowrap" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgId}}</div>
<div class="tb-cell" flex="20">{{event.body.msgType}}</div> <div class="tb-cell" flex="20" ng-mouseenter="checkTooltip($event)">{{event.body.msgType}}</div>
<div class="tb-cell" flex="20">{{event.body.dataType}}</div> <div class="tb-cell" flex="15">{{event.body.dataType}}</div>
<div class="tb-cell" flex="20"> <div class="tb-cell" flex="10">
<md-button ng-if="event.body.data" class="md-icon-button md-primary" <md-button ng-if="event.body.data" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.data, 'event.data', event.body.dataType)" ng-click="showContent($event, event.body.data, 'event.data', event.body.dataType)"
aria-label="{{ 'action.view' | translate }}"> aria-label="{{ 'action.view' | translate }}">
@ -35,7 +35,7 @@
</md-icon> </md-icon>
</md-button> </md-button>
</div> </div>
<div class="tb-cell" flex="20"> <div class="tb-cell" flex="10">
<md-button ng-if="event.body.metadata" class="md-icon-button md-primary" <md-button ng-if="event.body.metadata" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.metadata, 'event.metadata', 'JSON')" ng-click="showContent($event, event.body.metadata, 'event.metadata', 'JSON')"
aria-label="{{ 'action.view' | translate }}"> aria-label="{{ 'action.view' | translate }}">
@ -48,7 +48,7 @@
</md-icon> </md-icon>
</md-button> </md-button>
</div> </div>
<div class="tb-cell" flex="20"> <div class="tb-cell" flex="10">
<md-button ng-if="event.body.error" class="md-icon-button md-primary" <md-button ng-if="event.body.error" class="md-icon-button md-primary"
ng-click="showContent($event, event.body.error, 'event.error')" ng-click="showContent($event, event.body.error, 'event.error')"
aria-label="{{ 'action.view' | translate }}"> aria-label="{{ 'action.view' | translate }}">

View File

@ -86,6 +86,14 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
}); });
} }
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); $compile(element.contents())(scope);
} }

View File

@ -24,6 +24,17 @@ md-list.tb-event-table {
height: 48px; height: 48px;
padding: 0px; padding: 0px;
overflow: hidden; overflow: hidden;
.tb-cell {
text-overflow: ellipsis;
&.tb-scroll {
white-space: nowrap;
overflow-y: hidden;
overflow-x: auto;
}
&.tb-nowrap {
white-space: nowrap;
}
}
} }
.tb-row:hover { .tb-row:hover {
@ -39,13 +50,19 @@ md-list.tb-event-table {
color: rgba(0,0,0,.54); color: rgba(0,0,0,.54);
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
white-space: nowrap;
background: none; background: none;
white-space: nowrap;
} }
} }
.tb-cell { .tb-cell {
padding: 0 24px; &:first-child {
padding-left: 14px;
}
&:last-child {
padding-right: 14px;
}
padding: 0 6px;
margin: auto 0; margin: auto 0;
color: rgba(0,0,0,.87); color: rgba(0,0,0,.87);
font-size: 13px; font-size: 13px;

View File

@ -43,6 +43,7 @@ export default angular.module('thingsboard.locale', [])
"update": "Update", "update": "Update",
"remove": "Remove", "remove": "Remove",
"search": "Search", "search": "Search",
"clear-search": "Clear search",
"assign": "Assign", "assign": "Assign",
"unassign": "Unassign", "unassign": "Unassign",
"share": "Share", "share": "Share",
@ -1188,6 +1189,7 @@ export default angular.module('thingsboard.locale', [])
"details": "Details", "details": "Details",
"events": "Events", "events": "Events",
"search": "Search nodes", "search": "Search nodes",
"open-node-library": "Open node library",
"add": "Add rule node", "add": "Add rule node",
"name": "Name", "name": "Name",
"name-required": "Name is required.", "name-required": "Name is required.",

View File

@ -37,6 +37,8 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.$mdExpansionPanel = $mdExpansionPanel; vm.$mdExpansionPanel = $mdExpansionPanel;
vm.types = types; vm.types = types;
vm.isFullscreen = false;
vm.editingRuleNode = null; vm.editingRuleNode = null;
vm.isEditingRuleNode = false; vm.isEditingRuleNode = false;
@ -57,6 +59,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}; };
vm.ruleNodeTypesModel = {}; vm.ruleNodeTypesModel = {};
vm.ruleNodeTypesCanvasControl = {};
vm.ruleChainLibraryLoaded = false; vm.ruleChainLibraryLoaded = false;
for (var type in types.ruleNodeType) { for (var type in types.ruleNodeType) {
if (!types.ruleNodeType[type].special) { if (!types.ruleNodeType[type].special) {
@ -67,9 +70,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}, },
selectedObjects: [] selectedObjects: []
}; };
vm.ruleNodeTypesCanvasControl[type] = {};
} }
} }
vm.selectedObjects = []; vm.selectedObjects = [];
vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects);
@ -147,6 +153,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
theForm.$setPristine(); theForm.$setPristine();
vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
vm.editingRuleNode = angular.copy(vm.editingRuleNode); vm.editingRuleNode = angular.copy(vm.editingRuleNode);
updateRuleNodesHighlight();
} }
}; };
@ -313,12 +320,28 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
} }
}; };
loadRuleChainLibrary(); loadRuleChainLibrary(ruleNodeComponents, true);
function loadRuleChainLibrary() { $scope.$watch('vm.ruleNodeSearch',
function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) {
var res = $filter('filter')(ruleNodeComponents, {name: vm.ruleNodeSearch});
loadRuleChainLibrary(res);
}
}
);
$scope.$on('searchTextUpdated', function () {
updateRuleNodesHighlight();
});
function loadRuleChainLibrary(ruleNodeComponents, loadRuleChain) {
for (var componentType in vm.ruleNodeTypesModel) {
vm.ruleNodeTypesModel[componentType].model.nodes.length = 0;
}
for (var i=0;i<ruleNodeComponents.length;i++) { for (var i=0;i<ruleNodeComponents.length;i++) {
var ruleNodeComponent = ruleNodeComponents[i]; var ruleNodeComponent = ruleNodeComponents[i];
var componentType = ruleNodeComponent.type; componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model; var model = vm.ruleNodeTypesModel[componentType].model;
var node = { var node = {
id: 'node-lib-' + componentType + '-' + model.nodes.length, id: 'node-lib-' + componentType + '-' + model.nodes.length,
@ -349,8 +372,17 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
model.nodes.push(node); model.nodes.push(node);
} }
vm.ruleChainLibraryLoaded = true; vm.ruleChainLibraryLoaded = true;
if (loadRuleChain) {
prepareRuleChain(); prepareRuleChain();
} }
$mdUtil.nextTick(() => {
for (componentType in vm.ruleNodeTypesCanvasControl) {
if (vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize) {
vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize(true);
}
}
});
}
function prepareRuleChain() { function prepareRuleChain() {
@ -519,6 +551,8 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.isDirty = false; vm.isDirty = false;
updateRuleNodesHighlight();
$mdUtil.nextTick(() => { $mdUtil.nextTick(() => {
vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel',
function (newVal, oldVal) { function (newVal, oldVal) {
@ -530,6 +564,20 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}); });
} }
function updateRuleNodesHighlight() {
for (var i=0;i<vm.ruleChainModel.nodes.length;i++) {
vm.ruleChainModel.nodes[i].highlighted = false;
}
if ($scope.searchConfig.searchText) {
var res = $filter('filter')(vm.ruleChainModel.nodes, {name: $scope.searchConfig.searchText});
if (res) {
for (i=0;i<res.length;i++) {
res[i].highlighted = true;
}
}
}
}
function saveRuleChain() { function saveRuleChain() {
var ruleChainMetaData = { var ruleChainMetaData = {
ruleChainId: vm.ruleChain.id, ruleChainId: vm.ruleChain.id,
@ -642,6 +690,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
); );
} }
vm.ruleChainModel.nodes.push(ruleNode); vm.ruleChainModel.nodes.push(ruleNode);
updateRuleNodesHighlight();
}, function () { }, function () {
}); });
} }

View File

@ -76,7 +76,7 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
} }
}, },
data: { data: {
searchEnabled: false, searchEnabled: true,
pageTitle: 'rulechain.rulechain' pageTitle: 'rulechain.rulechain'
}, },
ncyBreadcrumb: { ncyBreadcrumb: {

View File

@ -125,6 +125,13 @@
color: #333; color: #333;
border: solid 1px #777; border: solid 1px #777;
font-size: 12px; font-size: 12px;
&.tb-rule-node-highlighted {
box-shadow: 0 0 10px 6px #51cbee;
.tb-node-title {
text-decoration: underline;
font-weight: bold;
}
}
&.tb-input-type { &.tb-input-type {
background-color: #a3eaa9; background-color: #a3eaa9;
user-select: none; user-select: none;
@ -156,7 +163,7 @@
} }
.tb-node-title { .tb-node-title {
font-weight: 600; font-weight: 500;
} }
.tb-node-type, .tb-node-title { .tb-node-type, .tb-node-title {
overflow: hidden; overflow: hidden;
@ -184,6 +191,10 @@
bottom: 0; bottom: 0;
background-color: #000; background-color: #000;
opacity: 0; opacity: 0;
/* &.tb-rule-node-highlighted {
background-color: green;
opacity: 0.15;
}*/
} }
&.fc-hover { &.fc-hover {
.fc-node-overlay { .fc-node-overlay {

View File

@ -19,17 +19,17 @@
<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty" <md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty"
expand-tooltip-direction="bottom" layout="column" class="tb-rulechain" expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"
ng-keydown="vm.keyDown($event)" ng-keydown="vm.keyDown($event)"
ng-keyup="vm.keyUp($event)"> ng-keyup="vm.keyUp($event)" on-fullscreen-changed="vm.isFullscreen = expanded">
<section class="tb-rulechain-container" flex layout="column"> <section class="tb-rulechain-container" flex layout="column">
<div class="tb-rulechain-layout" flex layout="row"> <div class="tb-rulechain-layout" flex layout="row">
<section layout="row" layout-wrap <section layout="row" layout-wrap
class="tb-header-buttons md-fab tb-library-open"> class="tb-header-buttons md-fab tb-library-open">
<md-button ng-show="!vm.isLibraryOpen" <md-button ng-show="!vm.isLibraryOpen"
class="tb-btn-header tb-btn-open-library md-primary md-fab md-fab-top-left" class="tb-btn-header tb-btn-open-library md-primary md-fab md-fab-top-left"
aria-label="{{ 'action.apply' | translate }}" aria-label="{{ 'rulenode.open-node-library' | translate }}"
ng-click="vm.isLibraryOpen = true"> ng-click="vm.isLibraryOpen = true">
<md-tooltip md-direction="top"> <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'action.apply-changes' | translate }} {{ 'rulenode.open-node-library' | translate }}
</md-tooltip> </md-tooltip>
<ng-md-icon icon="menu"></ng-md-icon> <ng-md-icon icon="menu"></ng-md-icon>
</md-button> </md-button>
@ -43,7 +43,7 @@
<div class="md-toolbar-tools"> <div class="md-toolbar-tools">
<md-button class="md-icon-button tb-small" aria-label="{{ 'action.search' | translate }}"> <md-button class="md-icon-button tb-small" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="top"> <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{'rulenode.search' | translate}} {{'rulenode.search' | translate}}
</md-tooltip> </md-tooltip>
</md-button> </md-button>
@ -53,15 +53,17 @@
<input ng-model="vm.ruleNodeSearch" placeholder="{{'rulenode.search' | translate}}"/> <input ng-model="vm.ruleNodeSearch" placeholder="{{'rulenode.search' | translate}}"/>
</md-input-container> </md-input-container>
</div> </div>
<md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.ruleNodeSearch = ''"> <md-button class="md-icon-button tb-small" aria-label="Close"
ng-show="vm.ruleNodeSearch"
ng-click="vm.ruleNodeSearch = ''">
<md-icon aria-label="Close" class="material-icons">close</md-icon> <md-icon aria-label="Close" class="material-icons">close</md-icon>
<md-tooltip md-direction="top"> <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'action.close' | translate }} {{ 'action.clear-search' | translate }}
</md-tooltip> </md-tooltip>
</md-button> </md-button>
<md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.isLibraryOpen = false"> <md-button class="md-icon-button tb-small" aria-label="Close" ng-click="vm.isLibraryOpen = false">
<md-icon aria-label="Close" class="material-icons">chevron_left</md-icon> <md-icon aria-label="Close" class="material-icons">chevron_left</md-icon>
<md-tooltip md-direction="top"> <md-tooltip md-direction="{{vm.isFullscreen ? 'bottom' : 'top'}}">
{{ 'action.close' | translate }} {{ 'action.close' | translate }}
</md-tooltip> </md-tooltip>
</md-button> </md-button>
@ -90,6 +92,7 @@
callbacks="vm.nodeLibCallbacks" callbacks="vm.nodeLibCallbacks"
node-width="170" node-width="170"
node-height="50" node-height="50"
control="vm.ruleNodeTypesCanvasControl[typeId]"
drop-target-id="'tb-rulchain-canvas'"></fc-canvas> drop-target-id="'tb-rulchain-canvas'"></fc-canvas>
</md-expansion-panel-content> </md-expansion-panel-content>
</md-expansion-panel-expanded> </md-expansion-panel-expanded>

View File

@ -22,8 +22,8 @@
ng-mousedown="callbacks.mouseDown($event, node)" ng-mousedown="callbacks.mouseDown($event, node)"
ng-mouseenter="callbacks.mouseEnter($event, node)" ng-mouseenter="callbacks.mouseEnter($event, node)"
ng-mouseleave="callbacks.mouseLeave($event, node)"> ng-mouseleave="callbacks.mouseLeave($event, node)">
<div class="{{flowchartConstants.nodeOverlayClass}}"></div> <div class="{{flowchartConstants.nodeOverlayClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted}"></div>
<div class="tb-rule-node {{node.nodeClass}}"> <div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted}">
<md-icon aria-label="node-type-icon" flex="15" <md-icon aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon> class="material-icons">{{node.icon}}</md-icon>
<div layout="column" flex="85" layout-align="center"> <div layout="column" flex="85" layout-align="center">