Browse Source

RuleNode Config UI

pull/725/head
Igor Kulikov 9 years ago
parent
commit
fd1199ee1c
  1. 2
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  2. 1
      pom.xml
  3. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
  4. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  5. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
  6. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  7. 21
      ui/server.js
  8. 59
      ui/src/app/api/rule-chain.service.js
  9. 34
      ui/src/app/components/js-func.directive.js
  10. 10
      ui/src/app/components/js-func.scss
  11. 15
      ui/src/app/components/js-func.tpl.html
  12. 4
      ui/src/app/locale/locale.constant.js
  13. 4
      ui/src/app/rulechain/index.js
  14. 21
      ui/src/app/rulechain/rulechain.controller.js
  15. 73
      ui/src/app/rulechain/rulenode-config.directive.js
  16. 32
      ui/src/app/rulechain/rulenode-config.tpl.html
  17. 63
      ui/src/app/rulechain/rulenode-defined-config.directive.js
  18. 9
      ui/src/app/rulechain/rulenode-fieldset.tpl.html
  19. 6
      ui/src/app/rulechain/rulenode.scss

2
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
NodeConfiguration config = configClazz.newInstance(); NodeConfiguration config = configClazz.newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration(); NodeConfiguration defaultConfiguration = config.defaultConfiguration();
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
nodeDefinition.setUiResources(nodeAnnotation.uiResources());
nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
return nodeDefinition; return nodeDefinition;
} }

1
pom.xml

@ -284,6 +284,7 @@
<exclude>src/sh/**</exclude> <exclude>src/sh/**</exclude>
<exclude>src/main/scripts/control/**</exclude> <exclude>src/main/scripts/control/**</exclude>
<exclude>src/main/scripts/windows/**</exclude> <exclude>src/main/scripts/windows/**</exclude>
<exclude>src/main/resources/public/static/rulenode/**</exclude>
</excludes> </excludes>
<mapping> <mapping>
<proto>JAVADOC_STYLE</proto> <proto>JAVADOC_STYLE</proto>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java

@ -29,5 +29,7 @@ public class NodeDefinition {
String[] relationTypes; String[] relationTypes;
boolean customRelations; boolean customRelations;
JsonNode defaultConfiguration; JsonNode defaultConfiguration;
String[] uiResources;
String configDirective;
} }

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -45,6 +45,10 @@ public @interface RuleNode {
String[] relationTypes() default {"Success", "Failure"}; String[] relationTypes() default {"Success", "Failure"};
String[] uiResources() default {};
String configDirective() default "";
boolean customRelations() default false; boolean customRelations() default false;
} }

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java

@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
nodeDetails = "Evaluate incoming Message with configured JS condition. " + nodeDetails = "Evaluate incoming Message with configured JS condition. " +
"If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
"Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" + "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
"Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>") "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeScriptConfig")
public class TbJsFilterNode implements TbNode { public class TbJsFilterNode implements TbNode {
private TbJsFilterNodeConfiguration config; private TbJsFilterNodeConfiguration config;

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

@ -0,0 +1,2 @@
!function(e){function t(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return e[r].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}([function(e,t,n){e.exports=n(3)},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args="{{ [\'msg\'] }}" no-validate=true> </tb-js-func> </section> '},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e){var t=function(t,n,r,u){var o=i.default;n.html(o),t.$watch("configuration",function(e,n){angular.equals(e,n)||u.$setViewValue(t.configuration)}),u.$render=function(){t.configuration=u.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}u.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(1),i=r(o)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=n(2),o=r(u),i=n(5),a=r(i);t.default=angular.module("thingsboard.ruleChain.config",[]).directive("tbFilterNodeScriptConfig",o.default).config(a.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e,t){(0,i.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}u.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(4),i=r(o)}]);
//# sourceMappingURL=rulenode-core-config.js.map

21
ui/server.js

@ -30,6 +30,10 @@ const httpProxy = require('http-proxy');
const forwardHost = 'localhost'; const forwardHost = 'localhost';
const forwardPort = 8080; const forwardPort = 8080;
const ruleNodeUiforwardHost = 'localhost';
const ruleNodeUiforwardPort = 8080;
//const ruleNodeUiforwardPort = 5000;
const app = express(); const app = express();
const server = http.createServer(app); const server = http.createServer(app);
@ -52,17 +56,34 @@ const apiProxy = httpProxy.createProxyServer({
} }
}); });
const ruleNodeUiApiProxy = httpProxy.createProxyServer({
target: {
host: ruleNodeUiforwardHost,
port: ruleNodeUiforwardPort
}
});
apiProxy.on('error', function (err, req, res) { apiProxy.on('error', function (err, req, res) {
console.warn('API proxy error: ' + err); console.warn('API proxy error: ' + err);
res.end('Error.'); res.end('Error.');
}); });
ruleNodeUiApiProxy.on('error', function (err, req, res) {
console.warn('RuleNode UI API proxy error: ' + err);
res.end('Error.');
});
console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`); console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
console.info(`Forwarding Rule Node UI requests to http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`);
app.all('/api/*', (req, res) => { app.all('/api/*', (req, res) => {
apiProxy.web(req, res); apiProxy.web(req, res);
}); });
app.all('/static/rulenode/*', (req, res) => {
ruleNodeUiApiProxy.web(req, res);
});
app.get('*', function(req, res) { app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'src/index.html')); res.sendFile(path.join(__dirname, 'src/index.html'));
}); });

59
ui/src/app/api/rule-chain.service.js

@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', [])
.factory('ruleChainService', RuleChainService).name; .factory('ruleChainService', RuleChainService).name;
/*@ngInject*/ /*@ngInject*/
function RuleChainService($http, $q, $filter, types, componentDescriptorService) { function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) {
var ruleNodeComponents = null; var ruleNodeComponents = null;
@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
} else { } else {
loadRuleNodeComponents().then( loadRuleNodeComponents().then(
(components) => { (components) => {
ruleNodeComponents = components; resolveRuleNodeComponentsUiResources(components).then(
ruleNodeComponents.push( (components) => {
types.ruleChainNodeComponent ruleNodeComponents = components;
ruleNodeComponents.push(
types.ruleChainNodeComponent
);
deferred.resolve(ruleNodeComponents);
},
() => {
deferred.reject();
}
); );
deferred.resolve(ruleNodeComponents);
}, },
() => { () => {
deferred.reject(); deferred.reject();
@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
return deferred.promise; return deferred.promise;
} }
function resolveRuleNodeComponentsUiResources(components) {
var deferred = $q.defer();
var tasks = [];
for (var i=0;i<components.length;i++) {
var component = components[i];
tasks.push(resolveRuleNodeComponentUiResources(component));
}
$q.all(tasks).then(
(components) => {
deferred.resolve(components);
},
() => {
deferred.resolve(components);
}
);
return deferred.promise;
}
function resolveRuleNodeComponentUiResources(component) {
var deferred = $q.defer();
var uiResources = component.configurationDescriptor.nodeDefinition.uiResources;
if (uiResources && uiResources.length) {
var tasks = [];
for (var i=0;i<uiResources.length;i++) {
var uiResource = uiResources[i];
tasks.push($ocLazyLoad.load(uiResource));
}
$q.all(tasks).then(
() => {
deferred.resolve(component);
},
() => {
component.configurationDescriptor.nodeDefinition.uiResourceLoadError = $translate.instant('rulenode.ui-resources-load-error');
deferred.resolve(component);
}
)
} else {
deferred.resolve(component);
}
return deferred.promise;
}
function getRuleNodeComponentByClazz(clazz) { function getRuleNodeComponentByClazz(clazz) {
var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true); var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
if (res && res.length) { if (res && res.length) {

34
ui/src/app/components/js-func.directive.js

@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
var template = $templateCache.get(jsFuncTemplate); var template = $templateCache.get(jsFuncTemplate);
element.html(template); element.html(template);
scope.functionName = attrs.functionName;
scope.functionArgs = scope.$eval(attrs.functionArgs); scope.functionArgs = scope.$eval(attrs.functionArgs);
scope.validationArgs = scope.$eval(attrs.validationArgs); scope.validationArgs = scope.$eval(attrs.validationArgs);
scope.resultType = attrs.resultType; scope.resultType = attrs.resultType;
@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.resultType = "nocheck"; scope.resultType = "nocheck";
} }
scope.validationTriggerArg = attrs.validationTriggerArg;
scope.functionValid = true; scope.functionValid = true;
var Range = ace.acequire("ace/range").Range; var Range = ace.acequire("ace/range").Range;
@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
} }
scope.onFullscreenChanged = function () { scope.onFullscreenChanged = function () {
updateEditorSize();
};
function updateEditorSize() {
if (scope.js_editor) { if (scope.js_editor) {
scope.js_editor.resize(); scope.js_editor.resize();
scope.js_editor.renderer.updateFull(); scope.js_editor.renderer.updateFull();
} }
}; }
scope.jsEditorOptions = { scope.jsEditorOptions = {
useWrapMode: true, useWrapMode: true,
@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.validate = function () { scope.validate = function () {
try { try {
var toValidate = new Function(scope.functionArgsString, scope.functionBody); var toValidate = new Function(scope.functionArgsString, scope.functionBody);
if (scope.noValidate) {
return true;
}
var res; var res;
var validationError; var validationError;
for (var i=0;i<scope.validationArgs.length;i++) { for (var i=0;i<scope.validationArgs.length;i++) {
@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
} }
}; };
scope.$on('form-submit', function () { scope.$on('form-submit', function (event, args) {
scope.functionValid = scope.validate(); if (!args || scope.validationTriggerArg && scope.validationTriggerArg == args) {
scope.updateValidity(); scope.validationArgs = scope.$eval(attrs.validationArgs);
scope.cleanupJsErrors();
scope.functionValid = true;
scope.updateValidity();
scope.functionValid = scope.validate();
scope.updateValidity();
}
});
scope.$on('update-ace-editor-size', function () {
updateEditorSize();
}); });
$compile(element.contents())(scope); $compile(element.contents())(scope);
@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
return { return {
restrict: "E", restrict: "E",
require: "^ngModel", require: "^ngModel",
scope: {}, scope: {
disabled:'=ngDisabled',
noValidate: '=?',
fillHeight:'=?'
},
link: linker link: linker
}; };
} }

10
ui/src/app/components/js-func.scss

@ -15,6 +15,12 @@
*/ */
tb-js-func { tb-js-func {
position: relative; position: relative;
.tb-disabled {
color: rgba(0,0,0,0.38);
}
.fill-height {
height: 100%;
}
} }
.tb-js-func-panel { .tb-js-func-panel {
@ -23,8 +29,10 @@ tb-js-func {
height: 100%; height: 100%;
#tb-javascript-input { #tb-javascript-input {
min-width: 200px; min-width: 200px;
min-height: 200px;
width: 100%; width: 100%;
height: 100%; height: 100%;
&:not(.fill-height) {
min-height: 200px;
}
} }
} }

15
ui/src/app/components/js-func.tpl.html

@ -15,19 +15,20 @@
limitations under the License. limitations under the License.
--> -->
<div style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> <div style="background: #fff;" ng-class="{'tb-disabled': disabled, 'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
<div layout="row" layout-align="start center" style="height: 40px;"> <div layout="row" layout-align="start center" style="height: 40px;">
<span style="font-style: italic;">function({{ functionArgsString }}) {</span> <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
<span flex></span> <span flex></span>
<div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div> <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
</div> </div>
<div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column">
<div flex id="tb-javascript-input" <div flex id="tb-javascript-input" ng-class="{'fill-height': fillHeight}"
ui-ace="jsEditorOptions" ui-ace="jsEditorOptions"
ng-readonly="disabled"
ng-model="functionBody"> ng-model="functionBody">
</div> </div>
</div> </div>
<div layout="row" layout-align="start center" style="height: 40px;"> <div layout="row" layout-align="start center" style="height: 40px;">
<span style="font-style: italic;">}</span> <label class="tb-title no-padding">}</label>
</div> </div>
</div> </div>

4
ui/src/app/locale/locale.constant.js

@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', [])
"type-action": "Action", "type-action": "Action",
"type-action-details": "Perform special action", "type-action-details": "Perform special action",
"type-rule-chain": "Rule Chain", "type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain" "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
"ui-resources-load-error": "Failed to load configuration ui resources."
}, },
"rule-plugin": { "rule-plugin": {
"management": "Rules and plugins management" "management": "Rules and plugins management"

4
ui/src/app/rulechain/index.js

@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes';
import RuleChainsController from './rulechains.controller'; import RuleChainsController from './rulechains.controller';
import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
import RuleChainDirective from './rulechain.directive'; import RuleChainDirective from './rulechain.directive';
import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive'; import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive'; import LinkDirective from './link.directive';
@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', [])
.controller('AddRuleNodeController', AddRuleNodeController) .controller('AddRuleNodeController', AddRuleNodeController)
.controller('AddRuleNodeLinkController', AddRuleNodeLinkController) .controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
.directive('tbRuleChain', RuleChainDirective) .directive('tbRuleChain', RuleChainDirective)
.directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
.directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective) .directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective) .directive('tbRuleNodeLink', LinkDirective)
.name; .name;

21
ui/src/app/rulechain/rulechain.controller.js

@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
}; };
vm.saveRuleNode = function(theForm) { vm.saveRuleNode = function(theForm) {
theForm.$setPristine(); $scope.$broadcast('form-submit');
vm.isEditingRuleNode = false; if (theForm.$valid) {
vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; theForm.$setPristine();
vm.editingRuleNode = angular.copy(vm.editingRuleNode); vm.isEditingRuleNode = false;
vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
vm.editingRuleNode = angular.copy(vm.editingRuleNode);
}
}; };
vm.saveRuleNodeLink = function(theForm) { vm.saveRuleNodeLink = function(theForm) {
@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var componentType = ruleNodeComponent.type; var componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model; var model = vm.ruleNodeTypesModel[componentType].model;
var node = { var node = {
id: model.nodes.length, id: 'node-lib-' + componentType + '-' + model.nodes.length,
component: ruleNodeComponent, component: ruleNodeComponent,
name: '', name: '',
nodeClass: vm.types.ruleNodeType[componentType].nodeClass, nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.ruleChainModel.nodes.push( vm.ruleChainModel.nodes.push(
{ {
id: vm.nextNodeID++, id: 'rule-chain-node-' + vm.nextNodeID++,
component: types.inputNodeComponent, component: types.inputNodeComponent,
name: "", name: "",
nodeClass: types.ruleNodeType.INPUT.nodeClass, nodeClass: types.ruleNodeType.INPUT.nodeClass,
@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) { if (component) {
var node = { var node = {
id: vm.nextNodeID++, id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id, ruleNodeId: ruleNode.id,
additionalInfo: ruleNode.additionalInfo, additionalInfo: ruleNode.additionalInfo,
configuration: ruleNode.configuration, configuration: ruleNode.configuration,
@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) { if (!ruleChainNode) {
ruleChainNode = { ruleChainNode = {
id: vm.nextNodeID++, id: 'rule-chain-node-' + vm.nextNodeID++,
additionalInfo: ruleChainConnection.additionalInfo, additionalInfo: ruleChainConnection.additionalInfo,
targetRuleChainId: ruleChainConnection.targetRuleChainId.id, targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
x: ruleChainConnection.additionalInfo.layoutX, x: ruleChainConnection.additionalInfo.layoutX,
@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
fullscreen: true, fullscreen: true,
targetEvent: $event targetEvent: $event
}).then(function (ruleNode) { }).then(function (ruleNode) {
ruleNode.id = vm.nextNodeID++; ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
ruleNode.connectors = []; ruleNode.connectors = [];
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
ruleNode.connectors.push( ruleNode.connectors.push(

73
ui/src/app/rulechain/rulenode-config.directive.js

@ -0,0 +1,73 @@
/*
* Copyright © 2016-2018 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 ruleNodeConfigTemplate from './rulenode-config.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function RuleNodeConfigDirective($compile, $templateCache, $injector, $translate) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(ruleNodeConfigTemplate);
element.html(template);
scope.$watch('configuration', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
ngModelCtrl.$setViewValue(scope.configuration);
}
});
ngModelCtrl.$render = function () {
scope.configuration = ngModelCtrl.$viewValue;
};
scope.useDefinedDirective = function() {
return scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
};
validateDefinedDirective();
function validateDefinedDirective() {
if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
scope.definedDirectiveError = scope.nodeDefinition.uiResourceLoadError;
} else {
var definedDirective = scope.nodeDefinition.configDirective;
if (definedDirective && definedDirective.length) {
if (!$injector.has(definedDirective + 'Directive')) {
scope.definedDirectiveError = $translate.instant('rulenode.directive-is-not-loaded', {directiveName: definedDirective});
}
}
}
}
$compile(element.contents())(scope);
};
return {
restrict: "E",
require: "^ngModel",
scope: {
nodeDefinition:'=',
required:'=ngRequired',
readonly:'=ngReadonly'
},
link: linker
};
}

32
ui/src/app/rulechain/rulenode-config.tpl.html

@ -0,0 +1,32 @@
<!--
Copyright © 2016-2018 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.
-->
<tb-rule-node-defined-config ng-if="useDefinedDirective()"
ng-model="configuration"
rule-node-directive="{{nodeDefinition.configDirective}}"
ng-required="required"
ng-readonly="readonly">
</tb-rule-node-defined-config>
<div class="tb-rulenode-directive-error" ng-if="definedDirectiveError">{{definedDirectiveError}}</div>
<tb-json-object-edit ng-if="!useDefinedDirective()"
class="tb-rule-node-configuration-json"
ng-model="configuration"
label="{{ 'rulenode.configuration' | translate }}"
ng-required="required"
fill-height="true">
</tb-json-object-edit>

63
ui/src/app/rulechain/rulenode-defined-config.directive.js

@ -0,0 +1,63 @@
/*
* Copyright © 2016-2018 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.
*/
const SNAKE_CASE_REGEXP = /[A-Z]/g;
/*@ngInject*/
export default function RuleNodeDefinedConfigDirective($compile) {
var linker = function (scope, element, attrs, ngModelCtrl) {
attrs.$observe('ruleNodeDirective', function() {
loadTemplate();
});
scope.$watch('configuration', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
ngModelCtrl.$setViewValue(scope.configuration);
}
});
ngModelCtrl.$render = function () {
scope.configuration = ngModelCtrl.$viewValue;
};
function loadTemplate() {
var directive = snake_case(attrs.ruleNodeDirective, '-');
var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
element.html(template);
$compile(element.contents())(scope);
}
function snake_case(name, separator) {
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
});
}
};
return {
restrict: "E",
require: "^ngModel",
scope: {
required:'=ngRequired',
readonly:'=ngReadonly'
},
link: linker
};
}

9
ui/src/app/rulechain/rulenode-fieldset.tpl.html

@ -38,11 +38,16 @@
ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
</md-checkbox> </md-checkbox>
</md-input-container> </md-input-container>
<tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration" <tb-rule-node-config ng-model="ruleNode.configuration"
ng-required="true"
node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
ng-readonly="$root.loading || !isEdit || isReadOnly">
</tb-rule-node-config>
<!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
label="{{ 'rulenode.configuration' | translate }}" label="{{ 'rulenode.configuration' | translate }}"
ng-required="true" ng-required="true"
fill-height="true"> fill-height="true">
</tb-json-object-edit> </tb-json-object-edit-->
<md-input-container class="md-block"> <md-input-container class="md-block">
<label translate>rulenode.description</label> <label translate>rulenode.description</label>
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>

6
ui/src/app/rulechain/rulenode.scss

@ -19,4 +19,10 @@
height: 300px; height: 300px;
display: block; display: block;
} }
}
.tb-rulenode-directive-error {
color: rgb(221,44,0);
font-size: 13px;
font-weight: 400;
} }
Loading…
Cancel
Save