Showing
19 changed files
with
336 additions
and
31 deletions
@@ -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; |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
0 → 100644
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> |