Commit fd1199ee1c3927c53377d40e750aaae7d8b72e06

Authored by Igor Kulikov
1 parent 5749fbf8

RuleNode Config UI

@@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
192 NodeConfiguration config = configClazz.newInstance(); 192 NodeConfiguration config = configClazz.newInstance();
193 NodeConfiguration defaultConfiguration = config.defaultConfiguration(); 193 NodeConfiguration defaultConfiguration = config.defaultConfiguration();
194 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); 194 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
  195 + nodeDefinition.setUiResources(nodeAnnotation.uiResources());
  196 + nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
195 return nodeDefinition; 197 return nodeDefinition;
196 } 198 }
197 199
@@ -284,6 +284,7 @@ @@ -284,6 +284,7 @@
284 <exclude>src/sh/**</exclude> 284 <exclude>src/sh/**</exclude>
285 <exclude>src/main/scripts/control/**</exclude> 285 <exclude>src/main/scripts/control/**</exclude>
286 <exclude>src/main/scripts/windows/**</exclude> 286 <exclude>src/main/scripts/windows/**</exclude>
  287 + <exclude>src/main/resources/public/static/rulenode/**</exclude>
287 </excludes> 288 </excludes>
288 <mapping> 289 <mapping>
289 <proto>JAVADOC_STYLE</proto> 290 <proto>JAVADOC_STYLE</proto>
@@ -29,5 +29,7 @@ public class NodeDefinition { @@ -29,5 +29,7 @@ public class NodeDefinition {
29 String[] relationTypes; 29 String[] relationTypes;
30 boolean customRelations; 30 boolean customRelations;
31 JsonNode defaultConfiguration; 31 JsonNode defaultConfiguration;
  32 + String[] uiResources;
  33 + String configDirective;
32 34
33 } 35 }
@@ -45,6 +45,10 @@ public @interface RuleNode { @@ -45,6 +45,10 @@ public @interface RuleNode {
45 45
46 String[] relationTypes() default {"Success", "Failure"}; 46 String[] relationTypes() default {"Success", "Failure"};
47 47
  48 + String[] uiResources() default {};
  49 +
  50 + String configDirective() default "";
  51 +
48 boolean customRelations() default false; 52 boolean customRelations() default false;
49 53
50 } 54 }
@@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
35 nodeDetails = "Evaluate incoming Message with configured JS condition. " + 35 nodeDetails = "Evaluate incoming Message with configured JS condition. " +
36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + 36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" + 37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
38 - "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>") 38 + "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>",
  39 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  40 + configDirective = "tbFilterNodeScriptConfig")
  41 +
39 public class TbJsFilterNode implements TbNode { 42 public class TbJsFilterNode implements TbNode {
40 43
41 private TbJsFilterNodeConfiguration config; 44 private TbJsFilterNodeConfiguration config;
  1 +!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)}]);
  2 +//# sourceMappingURL=rulenode-core-config.js.map
@@ -30,6 +30,10 @@ const httpProxy = require('http-proxy'); @@ -30,6 +30,10 @@ const httpProxy = require('http-proxy');
30 const forwardHost = 'localhost'; 30 const forwardHost = 'localhost';
31 const forwardPort = 8080; 31 const forwardPort = 8080;
32 32
  33 +const ruleNodeUiforwardHost = 'localhost';
  34 +const ruleNodeUiforwardPort = 8080;
  35 +//const ruleNodeUiforwardPort = 5000;
  36 +
33 const app = express(); 37 const app = express();
34 const server = http.createServer(app); 38 const server = http.createServer(app);
35 39
@@ -52,17 +56,34 @@ const apiProxy = httpProxy.createProxyServer({ @@ -52,17 +56,34 @@ const apiProxy = httpProxy.createProxyServer({
52 } 56 }
53 }); 57 });
54 58
  59 +const ruleNodeUiApiProxy = httpProxy.createProxyServer({
  60 + target: {
  61 + host: ruleNodeUiforwardHost,
  62 + port: ruleNodeUiforwardPort
  63 + }
  64 +});
  65 +
55 apiProxy.on('error', function (err, req, res) { 66 apiProxy.on('error', function (err, req, res) {
56 console.warn('API proxy error: ' + err); 67 console.warn('API proxy error: ' + err);
57 res.end('Error.'); 68 res.end('Error.');
58 }); 69 });
59 70
  71 +ruleNodeUiApiProxy.on('error', function (err, req, res) {
  72 + console.warn('RuleNode UI API proxy error: ' + err);
  73 + res.end('Error.');
  74 +});
  75 +
60 console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`); 76 console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
  77 +console.info(`Forwarding Rule Node UI requests to http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`);
61 78
62 app.all('/api/*', (req, res) => { 79 app.all('/api/*', (req, res) => {
63 apiProxy.web(req, res); 80 apiProxy.web(req, res);
64 }); 81 });
65 82
  83 +app.all('/static/rulenode/*', (req, res) => {
  84 + ruleNodeUiApiProxy.web(req, res);
  85 +});
  86 +
66 app.get('*', function(req, res) { 87 app.get('*', function(req, res) {
67 res.sendFile(path.join(__dirname, 'src/index.html')); 88 res.sendFile(path.join(__dirname, 'src/index.html'));
68 }); 89 });
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', []) @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', [])
17 .factory('ruleChainService', RuleChainService).name; 17 .factory('ruleChainService', RuleChainService).name;
18 18
19 /*@ngInject*/ 19 /*@ngInject*/
20 -function RuleChainService($http, $q, $filter, types, componentDescriptorService) { 20 +function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) {
21 21
22 var ruleNodeComponents = null; 22 var ruleNodeComponents = null;
23 23
@@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) @@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
177 } else { 177 } else {
178 loadRuleNodeComponents().then( 178 loadRuleNodeComponents().then(
179 (components) => { 179 (components) => {
180 - ruleNodeComponents = components;  
181 - ruleNodeComponents.push(  
182 - types.ruleChainNodeComponent 180 + resolveRuleNodeComponentsUiResources(components).then(
  181 + (components) => {
  182 + ruleNodeComponents = components;
  183 + ruleNodeComponents.push(
  184 + types.ruleChainNodeComponent
  185 + );
  186 + deferred.resolve(ruleNodeComponents);
  187 + },
  188 + () => {
  189 + deferred.reject();
  190 + }
183 ); 191 );
184 - deferred.resolve(ruleNodeComponents);  
185 }, 192 },
186 () => { 193 () => {
187 deferred.reject(); 194 deferred.reject();
@@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) @@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
191 return deferred.promise; 198 return deferred.promise;
192 } 199 }
193 200
  201 + function resolveRuleNodeComponentsUiResources(components) {
  202 + var deferred = $q.defer();
  203 + var tasks = [];
  204 + for (var i=0;i<components.length;i++) {
  205 + var component = components[i];
  206 + tasks.push(resolveRuleNodeComponentUiResources(component));
  207 + }
  208 + $q.all(tasks).then(
  209 + (components) => {
  210 + deferred.resolve(components);
  211 + },
  212 + () => {
  213 + deferred.resolve(components);
  214 + }
  215 + );
  216 + return deferred.promise;
  217 + }
  218 +
  219 + function resolveRuleNodeComponentUiResources(component) {
  220 + var deferred = $q.defer();
  221 + var uiResources = component.configurationDescriptor.nodeDefinition.uiResources;
  222 + if (uiResources && uiResources.length) {
  223 + var tasks = [];
  224 + for (var i=0;i<uiResources.length;i++) {
  225 + var uiResource = uiResources[i];
  226 + tasks.push($ocLazyLoad.load(uiResource));
  227 + }
  228 + $q.all(tasks).then(
  229 + () => {
  230 + deferred.resolve(component);
  231 + },
  232 + () => {
  233 + component.configurationDescriptor.nodeDefinition.uiResourceLoadError = $translate.instant('rulenode.ui-resources-load-error');
  234 + deferred.resolve(component);
  235 + }
  236 + )
  237 + } else {
  238 + deferred.resolve(component);
  239 + }
  240 + return deferred.promise;
  241 + }
  242 +
194 function getRuleNodeComponentByClazz(clazz) { 243 function getRuleNodeComponentByClazz(clazz) {
195 var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true); 244 var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
196 if (res && res.length) { 245 if (res && res.length) {
@@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
43 var template = $templateCache.get(jsFuncTemplate); 43 var template = $templateCache.get(jsFuncTemplate);
44 element.html(template); 44 element.html(template);
45 45
  46 + scope.functionName = attrs.functionName;
46 scope.functionArgs = scope.$eval(attrs.functionArgs); 47 scope.functionArgs = scope.$eval(attrs.functionArgs);
47 scope.validationArgs = scope.$eval(attrs.validationArgs); 48 scope.validationArgs = scope.$eval(attrs.validationArgs);
48 scope.resultType = attrs.resultType; 49 scope.resultType = attrs.resultType;
@@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
50 scope.resultType = "nocheck"; 51 scope.resultType = "nocheck";
51 } 52 }
52 53
  54 + scope.validationTriggerArg = attrs.validationTriggerArg;
  55 +
53 scope.functionValid = true; 56 scope.functionValid = true;
54 57
55 var Range = ace.acequire("ace/range").Range; 58 var Range = ace.acequire("ace/range").Range;
@@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
66 } 69 }
67 70
68 scope.onFullscreenChanged = function () { 71 scope.onFullscreenChanged = function () {
  72 + updateEditorSize();
  73 + };
  74 +
  75 + function updateEditorSize() {
69 if (scope.js_editor) { 76 if (scope.js_editor) {
70 scope.js_editor.resize(); 77 scope.js_editor.resize();
71 scope.js_editor.renderer.updateFull(); 78 scope.js_editor.renderer.updateFull();
72 } 79 }
73 - }; 80 + }
74 81
75 scope.jsEditorOptions = { 82 scope.jsEditorOptions = {
76 useWrapMode: true, 83 useWrapMode: true,
@@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
131 scope.validate = function () { 138 scope.validate = function () {
132 try { 139 try {
133 var toValidate = new Function(scope.functionArgsString, scope.functionBody); 140 var toValidate = new Function(scope.functionArgsString, scope.functionBody);
  141 + if (scope.noValidate) {
  142 + return true;
  143 + }
134 var res; 144 var res;
135 var validationError; 145 var validationError;
136 for (var i=0;i<scope.validationArgs.length;i++) { 146 for (var i=0;i<scope.validationArgs.length;i++) {
@@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
200 } 210 }
201 }; 211 };
202 212
203 - scope.$on('form-submit', function () {  
204 - scope.functionValid = scope.validate();  
205 - scope.updateValidity(); 213 + scope.$on('form-submit', function (event, args) {
  214 + if (!args || scope.validationTriggerArg && scope.validationTriggerArg == args) {
  215 + scope.validationArgs = scope.$eval(attrs.validationArgs);
  216 + scope.cleanupJsErrors();
  217 + scope.functionValid = true;
  218 + scope.updateValidity();
  219 + scope.functionValid = scope.validate();
  220 + scope.updateValidity();
  221 + }
  222 + });
  223 +
  224 + scope.$on('update-ace-editor-size', function () {
  225 + updateEditorSize();
206 }); 226 });
207 227
208 $compile(element.contents())(scope); 228 $compile(element.contents())(scope);
@@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { @@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
211 return { 231 return {
212 restrict: "E", 232 restrict: "E",
213 require: "^ngModel", 233 require: "^ngModel",
214 - scope: {}, 234 + scope: {
  235 + disabled:'=ngDisabled',
  236 + noValidate: '=?',
  237 + fillHeight:'=?'
  238 + },
215 link: linker 239 link: linker
216 }; 240 };
217 } 241 }
@@ -15,6 +15,12 @@ @@ -15,6 +15,12 @@
15 */ 15 */
16 tb-js-func { 16 tb-js-func {
17 position: relative; 17 position: relative;
  18 + .tb-disabled {
  19 + color: rgba(0,0,0,0.38);
  20 + }
  21 + .fill-height {
  22 + height: 100%;
  23 + }
18 } 24 }
19 25
20 .tb-js-func-panel { 26 .tb-js-func-panel {
@@ -23,8 +29,10 @@ tb-js-func { @@ -23,8 +29,10 @@ tb-js-func {
23 height: 100%; 29 height: 100%;
24 #tb-javascript-input { 30 #tb-javascript-input {
25 min-width: 200px; 31 min-width: 200px;
26 - min-height: 200px;  
27 width: 100%; 32 width: 100%;
28 height: 100%; 33 height: 100%;
  34 + &:not(.fill-height) {
  35 + min-height: 200px;
  36 + }
29 } 37 }
30 } 38 }
@@ -15,19 +15,20 @@ @@ -15,19 +15,20 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> 18 +<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">
19 <div layout="row" layout-align="start center" style="height: 40px;"> 19 <div layout="row" layout-align="start center" style="height: 40px;">
20 - <span style="font-style: italic;">function({{ functionArgsString }}) {</span> 20 + <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
21 <span flex></span> 21 <span flex></span>
22 <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div> 22 <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
23 </div> 23 </div>
24 <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> 24 <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column">
25 - <div flex id="tb-javascript-input"  
26 - ui-ace="jsEditorOptions" 25 + <div flex id="tb-javascript-input" ng-class="{'fill-height': fillHeight}"
  26 + ui-ace="jsEditorOptions"
  27 + ng-readonly="disabled"
27 ng-model="functionBody"> 28 ng-model="functionBody">
28 </div> 29 </div>
29 </div> 30 </div>
30 <div layout="row" layout-align="start center" style="height: 40px;"> 31 <div layout="row" layout-align="start center" style="height: 40px;">
31 - <span style="font-style: italic;">}</span>  
32 - </div>  
33 -</div>  
  32 + <label class="tb-title no-padding">}</label>
  33 + </div>
  34 +</div>
@@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', []) @@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', [])
1198 "type-action": "Action", 1198 "type-action": "Action",
1199 "type-action-details": "Perform special action", 1199 "type-action-details": "Perform special action",
1200 "type-rule-chain": "Rule Chain", 1200 "type-rule-chain": "Rule Chain",
1201 - "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain" 1201 + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
  1202 + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
  1203 + "ui-resources-load-error": "Failed to load configuration ui resources."
1202 }, 1204 },
1203 "rule-plugin": { 1205 "rule-plugin": {
1204 "management": "Rules and plugins management" 1206 "management": "Rules and plugins management"
@@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes'; @@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes';
18 import RuleChainsController from './rulechains.controller'; 18 import RuleChainsController from './rulechains.controller';
19 import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; 19 import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
20 import RuleChainDirective from './rulechain.directive'; 20 import RuleChainDirective from './rulechain.directive';
  21 +import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
  22 +import RuleNodeConfigDirective from './rulenode-config.directive';
21 import RuleNodeDirective from './rulenode.directive'; 23 import RuleNodeDirective from './rulenode.directive';
22 import LinkDirective from './link.directive'; 24 import LinkDirective from './link.directive';
23 25
@@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', []) @@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', [])
28 .controller('AddRuleNodeController', AddRuleNodeController) 30 .controller('AddRuleNodeController', AddRuleNodeController)
29 .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) 31 .controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
30 .directive('tbRuleChain', RuleChainDirective) 32 .directive('tbRuleChain', RuleChainDirective)
  33 + .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
  34 + .directive('tbRuleNodeConfig', RuleNodeConfigDirective)
31 .directive('tbRuleNode', RuleNodeDirective) 35 .directive('tbRuleNode', RuleNodeDirective)
32 .directive('tbRuleNodeLink', LinkDirective) 36 .directive('tbRuleNodeLink', LinkDirective)
33 .name; 37 .name;
@@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
137 }; 137 };
138 138
139 vm.saveRuleNode = function(theForm) { 139 vm.saveRuleNode = function(theForm) {
140 - theForm.$setPristine();  
141 - vm.isEditingRuleNode = false;  
142 - vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;  
143 - vm.editingRuleNode = angular.copy(vm.editingRuleNode); 140 + $scope.$broadcast('form-submit');
  141 + if (theForm.$valid) {
  142 + theForm.$setPristine();
  143 + vm.isEditingRuleNode = false;
  144 + vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
  145 + vm.editingRuleNode = angular.copy(vm.editingRuleNode);
  146 + }
144 }; 147 };
145 148
146 vm.saveRuleNodeLink = function(theForm) { 149 vm.saveRuleNodeLink = function(theForm) {
@@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
309 var componentType = ruleNodeComponent.type; 312 var componentType = ruleNodeComponent.type;
310 var model = vm.ruleNodeTypesModel[componentType].model; 313 var model = vm.ruleNodeTypesModel[componentType].model;
311 var node = { 314 var node = {
312 - id: model.nodes.length, 315 + id: 'node-lib-' + componentType + '-' + model.nodes.length,
313 component: ruleNodeComponent, 316 component: ruleNodeComponent,
314 name: '', 317 name: '',
315 nodeClass: vm.types.ruleNodeType[componentType].nodeClass, 318 nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
@@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
358 361
359 vm.ruleChainModel.nodes.push( 362 vm.ruleChainModel.nodes.push(
360 { 363 {
361 - id: vm.nextNodeID++, 364 + id: 'rule-chain-node-' + vm.nextNodeID++,
362 component: types.inputNodeComponent, 365 component: types.inputNodeComponent,
363 name: "", 366 name: "",
364 nodeClass: types.ruleNodeType.INPUT.nodeClass, 367 nodeClass: types.ruleNodeType.INPUT.nodeClass,
@@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
389 var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); 392 var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
390 if (component) { 393 if (component) {
391 var node = { 394 var node = {
392 - id: vm.nextNodeID++, 395 + id: 'rule-chain-node-' + vm.nextNodeID++,
393 ruleNodeId: ruleNode.id, 396 ruleNodeId: ruleNode.id,
394 additionalInfo: ruleNode.additionalInfo, 397 additionalInfo: ruleNode.additionalInfo,
395 configuration: ruleNode.configuration, 398 configuration: ruleNode.configuration,
@@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
466 var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; 469 var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
467 if (!ruleChainNode) { 470 if (!ruleChainNode) {
468 ruleChainNode = { 471 ruleChainNode = {
469 - id: vm.nextNodeID++, 472 + id: 'rule-chain-node-' + vm.nextNodeID++,
470 additionalInfo: ruleChainConnection.additionalInfo, 473 additionalInfo: ruleChainConnection.additionalInfo,
471 targetRuleChainId: ruleChainConnection.targetRuleChainId.id, 474 targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
472 x: ruleChainConnection.additionalInfo.layoutX, 475 x: ruleChainConnection.additionalInfo.layoutX,
@@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
611 fullscreen: true, 614 fullscreen: true,
612 targetEvent: $event 615 targetEvent: $event
613 }).then(function (ruleNode) { 616 }).then(function (ruleNode) {
614 - ruleNode.id = vm.nextNodeID++; 617 + ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
615 ruleNode.connectors = []; 618 ruleNode.connectors = [];
616 if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { 619 if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
617 ruleNode.connectors.push( 620 ruleNode.connectors.push(
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import ruleNodeConfigTemplate from './rulenode-config.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +/*@ngInject*/
  24 +export default function RuleNodeConfigDirective($compile, $templateCache, $injector, $translate) {
  25 +
  26 + var linker = function (scope, element, attrs, ngModelCtrl) {
  27 + var template = $templateCache.get(ruleNodeConfigTemplate);
  28 + element.html(template);
  29 +
  30 + scope.$watch('configuration', function (newVal, prevVal) {
  31 + if (!angular.equals(newVal, prevVal)) {
  32 + ngModelCtrl.$setViewValue(scope.configuration);
  33 + }
  34 + });
  35 +
  36 + ngModelCtrl.$render = function () {
  37 + scope.configuration = ngModelCtrl.$viewValue;
  38 + };
  39 +
  40 + scope.useDefinedDirective = function() {
  41 + return scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
  42 + };
  43 +
  44 + validateDefinedDirective();
  45 +
  46 + function validateDefinedDirective() {
  47 + if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
  48 + scope.definedDirectiveError = scope.nodeDefinition.uiResourceLoadError;
  49 + } else {
  50 + var definedDirective = scope.nodeDefinition.configDirective;
  51 + if (definedDirective && definedDirective.length) {
  52 + if (!$injector.has(definedDirective + 'Directive')) {
  53 + scope.definedDirectiveError = $translate.instant('rulenode.directive-is-not-loaded', {directiveName: definedDirective});
  54 + }
  55 + }
  56 + }
  57 + }
  58 +
  59 + $compile(element.contents())(scope);
  60 + };
  61 +
  62 + return {
  63 + restrict: "E",
  64 + require: "^ngModel",
  65 + scope: {
  66 + nodeDefinition:'=',
  67 + required:'=ngRequired',
  68 + readonly:'=ngReadonly'
  69 + },
  70 + link: linker
  71 + };
  72 +
  73 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2018 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<tb-rule-node-defined-config ng-if="useDefinedDirective()"
  20 + ng-model="configuration"
  21 + rule-node-directive="{{nodeDefinition.configDirective}}"
  22 + ng-required="required"
  23 + ng-readonly="readonly">
  24 +</tb-rule-node-defined-config>
  25 +<div class="tb-rulenode-directive-error" ng-if="definedDirectiveError">{{definedDirectiveError}}</div>
  26 +<tb-json-object-edit ng-if="!useDefinedDirective()"
  27 + class="tb-rule-node-configuration-json"
  28 + ng-model="configuration"
  29 + label="{{ 'rulenode.configuration' | translate }}"
  30 + ng-required="required"
  31 + fill-height="true">
  32 +</tb-json-object-edit>
  1 +/*
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +const SNAKE_CASE_REGEXP = /[A-Z]/g;
  18 +
  19 +/*@ngInject*/
  20 +export default function RuleNodeDefinedConfigDirective($compile) {
  21 +
  22 + var linker = function (scope, element, attrs, ngModelCtrl) {
  23 +
  24 + attrs.$observe('ruleNodeDirective', function() {
  25 + loadTemplate();
  26 + });
  27 +
  28 + scope.$watch('configuration', function (newVal, prevVal) {
  29 + if (!angular.equals(newVal, prevVal)) {
  30 + ngModelCtrl.$setViewValue(scope.configuration);
  31 + }
  32 + });
  33 +
  34 + ngModelCtrl.$render = function () {
  35 + scope.configuration = ngModelCtrl.$viewValue;
  36 + };
  37 +
  38 + function loadTemplate() {
  39 + var directive = snake_case(attrs.ruleNodeDirective, '-');
  40 + var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
  41 + element.html(template);
  42 + $compile(element.contents())(scope);
  43 + }
  44 +
  45 + function snake_case(name, separator) {
  46 + separator = separator || '_';
  47 + return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
  48 + return (pos ? separator : '') + letter.toLowerCase();
  49 + });
  50 + }
  51 + };
  52 +
  53 + return {
  54 + restrict: "E",
  55 + require: "^ngModel",
  56 + scope: {
  57 + required:'=ngRequired',
  58 + readonly:'=ngReadonly'
  59 + },
  60 + link: linker
  61 + };
  62 +
  63 +}
@@ -38,11 +38,16 @@ @@ -38,11 +38,16 @@
38 ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} 38 ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
39 </md-checkbox> 39 </md-checkbox>
40 </md-input-container> 40 </md-input-container>
41 - <tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration" 41 + <tb-rule-node-config ng-model="ruleNode.configuration"
  42 + ng-required="true"
  43 + node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
  44 + ng-readonly="$root.loading || !isEdit || isReadOnly">
  45 + </tb-rule-node-config>
  46 + <!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
42 label="{{ 'rulenode.configuration' | translate }}" 47 label="{{ 'rulenode.configuration' | translate }}"
43 ng-required="true" 48 ng-required="true"
44 fill-height="true"> 49 fill-height="true">
45 - </tb-json-object-edit> 50 + </tb-json-object-edit-->
46 <md-input-container class="md-block"> 51 <md-input-container class="md-block">
47 <label translate>rulenode.description</label> 52 <label translate>rulenode.description</label>
48 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> 53 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
@@ -19,4 +19,10 @@ @@ -19,4 +19,10 @@
19 height: 300px; 19 height: 300px;
20 display: block; 20 display: block;
21 } 21 }
  22 +}
  23 +
  24 +.tb-rulenode-directive-error {
  25 + color: rgb(221,44,0);
  26 + font-size: 13px;
  27 + font-weight: 400;
22 } 28 }