Commit fd1199ee1c3927c53377d40e750aaae7d8b72e06

Authored by Igor Kulikov
1 parent 5749fbf8

RuleNode Config UI

... ... @@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
192 192 NodeConfiguration config = configClazz.newInstance();
193 193 NodeConfiguration defaultConfiguration = config.defaultConfiguration();
194 194 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
  195 + nodeDefinition.setUiResources(nodeAnnotation.uiResources());
  196 + nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
195 197 return nodeDefinition;
196 198 }
197 199
... ...
... ... @@ -284,6 +284,7 @@
284 284 <exclude>src/sh/**</exclude>
285 285 <exclude>src/main/scripts/control/**</exclude>
286 286 <exclude>src/main/scripts/windows/**</exclude>
  287 + <exclude>src/main/resources/public/static/rulenode/**</exclude>
287 288 </excludes>
288 289 <mapping>
289 290 <proto>JAVADOC_STYLE</proto>
... ...
... ... @@ -29,5 +29,7 @@ public class NodeDefinition {
29 29 String[] relationTypes;
30 30 boolean customRelations;
31 31 JsonNode defaultConfiguration;
  32 + String[] uiResources;
  33 + String configDirective;
32 34
33 35 }
... ...
... ... @@ -45,6 +45,10 @@ public @interface RuleNode {
45 45
46 46 String[] relationTypes() default {"Success", "Failure"};
47 47
  48 + String[] uiResources() default {};
  49 +
  50 + String configDirective() default "";
  51 +
48 52 boolean customRelations() default false;
49 53
50 54 }
... ...
... ... @@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
35 35 nodeDetails = "Evaluate incoming Message with configured JS condition. " +
36 36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
37 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 42 public class TbJsFilterNode implements TbNode {
40 43
41 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
\ No newline at end of file
... ...
... ... @@ -30,6 +30,10 @@ const httpProxy = require('http-proxy');
30 30 const forwardHost = 'localhost';
31 31 const forwardPort = 8080;
32 32
  33 +const ruleNodeUiforwardHost = 'localhost';
  34 +const ruleNodeUiforwardPort = 8080;
  35 +//const ruleNodeUiforwardPort = 5000;
  36 +
33 37 const app = express();
34 38 const server = http.createServer(app);
35 39
... ... @@ -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 66 apiProxy.on('error', function (err, req, res) {
56 67 console.warn('API proxy error: ' + err);
57 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 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 79 app.all('/api/*', (req, res) => {
63 80 apiProxy.web(req, res);
64 81 });
65 82
  83 +app.all('/static/rulenode/*', (req, res) => {
  84 + ruleNodeUiApiProxy.web(req, res);
  85 +});
  86 +
66 87 app.get('*', function(req, res) {
67 88 res.sendFile(path.join(__dirname, 'src/index.html'));
68 89 });
... ...
... ... @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', [])
17 17 .factory('ruleChainService', RuleChainService).name;
18 18
19 19 /*@ngInject*/
20   -function RuleChainService($http, $q, $filter, types, componentDescriptorService) {
  20 +function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) {
21 21
22 22 var ruleNodeComponents = null;
23 23
... ... @@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
177 177 } else {
178 178 loadRuleNodeComponents().then(
179 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 194 deferred.reject();
... ... @@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
191 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 243 function getRuleNodeComponentByClazz(clazz) {
195 244 var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
196 245 if (res && res.length) {
... ...
... ... @@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
43 43 var template = $templateCache.get(jsFuncTemplate);
44 44 element.html(template);
45 45
  46 + scope.functionName = attrs.functionName;
46 47 scope.functionArgs = scope.$eval(attrs.functionArgs);
47 48 scope.validationArgs = scope.$eval(attrs.validationArgs);
48 49 scope.resultType = attrs.resultType;
... ... @@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
50 51 scope.resultType = "nocheck";
51 52 }
52 53
  54 + scope.validationTriggerArg = attrs.validationTriggerArg;
  55 +
53 56 scope.functionValid = true;
54 57
55 58 var Range = ace.acequire("ace/range").Range;
... ... @@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
66 69 }
67 70
68 71 scope.onFullscreenChanged = function () {
  72 + updateEditorSize();
  73 + };
  74 +
  75 + function updateEditorSize() {
69 76 if (scope.js_editor) {
70 77 scope.js_editor.resize();
71 78 scope.js_editor.renderer.updateFull();
72 79 }
73   - };
  80 + }
74 81
75 82 scope.jsEditorOptions = {
76 83 useWrapMode: true,
... ... @@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
131 138 scope.validate = function () {
132 139 try {
133 140 var toValidate = new Function(scope.functionArgsString, scope.functionBody);
  141 + if (scope.noValidate) {
  142 + return true;
  143 + }
134 144 var res;
135 145 var validationError;
136 146 for (var i=0;i<scope.validationArgs.length;i++) {
... ... @@ -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 228 $compile(element.contents())(scope);
... ... @@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
211 231 return {
212 232 restrict: "E",
213 233 require: "^ngModel",
214   - scope: {},
  234 + scope: {
  235 + disabled:'=ngDisabled',
  236 + noValidate: '=?',
  237 + fillHeight:'=?'
  238 + },
215 239 link: linker
216 240 };
217 241 }
... ...
... ... @@ -15,6 +15,12 @@
15 15 */
16 16 tb-js-func {
17 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 26 .tb-js-func-panel {
... ... @@ -23,8 +29,10 @@ tb-js-func {
23 29 height: 100%;
24 30 #tb-javascript-input {
25 31 min-width: 200px;
26   - min-height: 200px;
27 32 width: 100%;
28 33 height: 100%;
  34 + &:not(.fill-height) {
  35 + min-height: 200px;
  36 + }
29 37 }
30 38 }
... ...
... ... @@ -15,19 +15,20 @@
15 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 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 21 <span flex></span>
22 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 23 </div>
24 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 28 ng-model="functionBody">
28 29 </div>
29 30 </div>
30 31 <div layout="row" layout-align="start center" style="height: 40px;">
31   - <span style="font-style: italic;">}</span>
32   - </div>
33   -</div>
\ No newline at end of file
  32 + <label class="tb-title no-padding">}</label>
  33 + </div>
  34 +</div>
... ...
... ... @@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', [])
1198 1198 "type-action": "Action",
1199 1199 "type-action-details": "Perform special action",
1200 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 1205 "rule-plugin": {
1204 1206 "management": "Rules and plugins management"
... ...
... ... @@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes';
18 18 import RuleChainsController from './rulechains.controller';
19 19 import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
20 20 import RuleChainDirective from './rulechain.directive';
  21 +import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
  22 +import RuleNodeConfigDirective from './rulenode-config.directive';
21 23 import RuleNodeDirective from './rulenode.directive';
22 24 import LinkDirective from './link.directive';
23 25
... ... @@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', [])
28 30 .controller('AddRuleNodeController', AddRuleNodeController)
29 31 .controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
30 32 .directive('tbRuleChain', RuleChainDirective)
  33 + .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
  34 + .directive('tbRuleNodeConfig', RuleNodeConfigDirective)
31 35 .directive('tbRuleNode', RuleNodeDirective)
32 36 .directive('tbRuleNodeLink', LinkDirective)
33 37 .name;
... ...
... ... @@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
137 137 };
138 138
139 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 149 vm.saveRuleNodeLink = function(theForm) {
... ... @@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
309 312 var componentType = ruleNodeComponent.type;
310 313 var model = vm.ruleNodeTypesModel[componentType].model;
311 314 var node = {
312   - id: model.nodes.length,
  315 + id: 'node-lib-' + componentType + '-' + model.nodes.length,
313 316 component: ruleNodeComponent,
314 317 name: '',
315 318 nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
... ... @@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
358 361
359 362 vm.ruleChainModel.nodes.push(
360 363 {
361   - id: vm.nextNodeID++,
  364 + id: 'rule-chain-node-' + vm.nextNodeID++,
362 365 component: types.inputNodeComponent,
363 366 name: "",
364 367 nodeClass: types.ruleNodeType.INPUT.nodeClass,
... ... @@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
389 392 var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
390 393 if (component) {
391 394 var node = {
392   - id: vm.nextNodeID++,
  395 + id: 'rule-chain-node-' + vm.nextNodeID++,
393 396 ruleNodeId: ruleNode.id,
394 397 additionalInfo: ruleNode.additionalInfo,
395 398 configuration: ruleNode.configuration,
... ... @@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
466 469 var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
467 470 if (!ruleChainNode) {
468 471 ruleChainNode = {
469   - id: vm.nextNodeID++,
  472 + id: 'rule-chain-node-' + vm.nextNodeID++,
470 473 additionalInfo: ruleChainConnection.additionalInfo,
471 474 targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
472 475 x: ruleChainConnection.additionalInfo.layoutX,
... ... @@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
611 614 fullscreen: true,
612 615 targetEvent: $event
613 616 }).then(function (ruleNode) {
614   - ruleNode.id = vm.nextNodeID++;
  617 + ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
615 618 ruleNode.connectors = [];
616 619 if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
617 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 38 ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
39 39 </md-checkbox>
40 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 47 label="{{ 'rulenode.configuration' | translate }}"
43 48 ng-required="true"
44 49 fill-height="true">
45   - </tb-json-object-edit>
  50 + </tb-json-object-edit-->
46 51 <md-input-container class="md-block">
47 52 <label translate>rulenode.description</label>
48 53 <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>
... ...
... ... @@ -19,4 +19,10 @@
19 19 height: 300px;
20 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 }
\ No newline at end of file
... ...