thingsboard-memoizeit
Changes
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java 2(+2 -0)
pom.xml 1(+1 -0)
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java 2(+2 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java 5(+4 -1)
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js 2(+2 -0)
ui/server.js 21(+21 -0)
ui/src/app/api/rule-chain.service.js 59(+54 -5)
ui/src/app/components/js-func.directive.js 34(+29 -5)
ui/src/app/components/js-func.scss 10(+9 -1)
ui/src/app/components/js-func.tpl.html 15(+8 -7)
ui/src/app/locale/locale.constant.js 4(+3 -1)
ui/src/app/rulechain/index.js 4(+4 -0)
ui/src/app/rulechain/rulechain.controller.js 21(+12 -9)
ui/src/app/rulechain/rulenode.scss 6(+6 -0)
Details
diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
index 479f424..9377756 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
@@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
NodeConfiguration config = configClazz.newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration();
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
+ nodeDefinition.setUiResources(nodeAnnotation.uiResources());
+ nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
return nodeDefinition;
}
pom.xml 1(+1 -0)
diff --git a/pom.xml b/pom.xml
index f0c915a..a90a8aa 100755
--- a/pom.xml
+++ b/pom.xml
@@ -284,6 +284,7 @@
<exclude>src/sh/**</exclude>
<exclude>src/main/scripts/control/**</exclude>
<exclude>src/main/scripts/windows/**</exclude>
+ <exclude>src/main/resources/public/static/rulenode/**</exclude>
</excludes>
<mapping>
<proto>JAVADOC_STYLE</proto>
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
index 6c57d92..18b2b94 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
@@ -29,5 +29,7 @@ public class NodeDefinition {
String[] relationTypes;
boolean customRelations;
JsonNode defaultConfiguration;
+ String[] uiResources;
+ String configDirective;
}
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
index 1617034..eea92ed 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
@@ -45,6 +45,10 @@ public @interface RuleNode {
String[] relationTypes() default {"Success", "Failure"};
+ String[] uiResources() default {};
+
+ String configDirective() default "";
+
boolean customRelations() default false;
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
index 07b166d..c684b20 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
@@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
nodeDetails = "Evaluate incoming Message with configured JS condition. " +
"If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
"Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
- "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>")
+ "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbFilterNodeScriptConfig")
+
public class TbJsFilterNode implements TbNode {
private TbJsFilterNodeConfiguration config;
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
new file mode 100644
index 0000000..f254cf5
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
@@ -0,0 +1,2 @@
+!function(e){function t(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return e[r].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}([function(e,t,n){e.exports=n(3)},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args="{{ [\'msg\'] }}" no-validate=true> </tb-js-func> </section> '},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e){var t=function(t,n,r,u){var o=i.default;n.html(o),t.$watch("configuration",function(e,n){angular.equals(e,n)||u.$setViewValue(t.configuration)}),u.$render=function(){t.configuration=u.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}u.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(1),i=r(o)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=n(2),o=r(u),i=n(5),a=r(i);t.default=angular.module("thingsboard.ruleChain.config",[]).directive("tbFilterNodeScriptConfig",o.default).config(a.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e,t){(0,i.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}u.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(4),i=r(o)}]);
+//# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
ui/server.js 21(+21 -0)
diff --git a/ui/server.js b/ui/server.js
index fae132f..0513600 100644
--- a/ui/server.js
+++ b/ui/server.js
@@ -30,6 +30,10 @@ const httpProxy = require('http-proxy');
const forwardHost = 'localhost';
const forwardPort = 8080;
+const ruleNodeUiforwardHost = 'localhost';
+const ruleNodeUiforwardPort = 8080;
+//const ruleNodeUiforwardPort = 5000;
+
const app = express();
const server = http.createServer(app);
@@ -52,17 +56,34 @@ const apiProxy = httpProxy.createProxyServer({
}
});
+const ruleNodeUiApiProxy = httpProxy.createProxyServer({
+ target: {
+ host: ruleNodeUiforwardHost,
+ port: ruleNodeUiforwardPort
+ }
+});
+
apiProxy.on('error', function (err, req, res) {
console.warn('API proxy error: ' + err);
res.end('Error.');
});
+ruleNodeUiApiProxy.on('error', function (err, req, res) {
+ console.warn('RuleNode UI API proxy error: ' + err);
+ res.end('Error.');
+});
+
console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
+console.info(`Forwarding Rule Node UI requests to http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`);
app.all('/api/*', (req, res) => {
apiProxy.web(req, res);
});
+app.all('/static/rulenode/*', (req, res) => {
+ ruleNodeUiApiProxy.web(req, res);
+});
+
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'src/index.html'));
});
ui/src/app/api/rule-chain.service.js 59(+54 -5)
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index ebc48fa..af14a3f 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', [])
.factory('ruleChainService', RuleChainService).name;
/*@ngInject*/
-function RuleChainService($http, $q, $filter, types, componentDescriptorService) {
+function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) {
var ruleNodeComponents = null;
@@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
} else {
loadRuleNodeComponents().then(
(components) => {
- ruleNodeComponents = components;
- ruleNodeComponents.push(
- types.ruleChainNodeComponent
+ resolveRuleNodeComponentsUiResources(components).then(
+ (components) => {
+ ruleNodeComponents = components;
+ ruleNodeComponents.push(
+ types.ruleChainNodeComponent
+ );
+ deferred.resolve(ruleNodeComponents);
+ },
+ () => {
+ deferred.reject();
+ }
);
- deferred.resolve(ruleNodeComponents);
},
() => {
deferred.reject();
@@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService)
return deferred.promise;
}
+ function resolveRuleNodeComponentsUiResources(components) {
+ var deferred = $q.defer();
+ var tasks = [];
+ for (var i=0;i<components.length;i++) {
+ var component = components[i];
+ tasks.push(resolveRuleNodeComponentUiResources(component));
+ }
+ $q.all(tasks).then(
+ (components) => {
+ deferred.resolve(components);
+ },
+ () => {
+ deferred.resolve(components);
+ }
+ );
+ return deferred.promise;
+ }
+
+ function resolveRuleNodeComponentUiResources(component) {
+ var deferred = $q.defer();
+ var uiResources = component.configurationDescriptor.nodeDefinition.uiResources;
+ if (uiResources && uiResources.length) {
+ var tasks = [];
+ for (var i=0;i<uiResources.length;i++) {
+ var uiResource = uiResources[i];
+ tasks.push($ocLazyLoad.load(uiResource));
+ }
+ $q.all(tasks).then(
+ () => {
+ deferred.resolve(component);
+ },
+ () => {
+ component.configurationDescriptor.nodeDefinition.uiResourceLoadError = $translate.instant('rulenode.ui-resources-load-error');
+ deferred.resolve(component);
+ }
+ )
+ } else {
+ deferred.resolve(component);
+ }
+ return deferred.promise;
+ }
+
function getRuleNodeComponentByClazz(clazz) {
var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true);
if (res && res.length) {
ui/src/app/components/js-func.directive.js 34(+29 -5)
diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js
index 33cebde..deb5626 100644
--- a/ui/src/app/components/js-func.directive.js
+++ b/ui/src/app/components/js-func.directive.js
@@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
var template = $templateCache.get(jsFuncTemplate);
element.html(template);
+ scope.functionName = attrs.functionName;
scope.functionArgs = scope.$eval(attrs.functionArgs);
scope.validationArgs = scope.$eval(attrs.validationArgs);
scope.resultType = attrs.resultType;
@@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.resultType = "nocheck";
}
+ scope.validationTriggerArg = attrs.validationTriggerArg;
+
scope.functionValid = true;
var Range = ace.acequire("ace/range").Range;
@@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
}
scope.onFullscreenChanged = function () {
+ updateEditorSize();
+ };
+
+ function updateEditorSize() {
if (scope.js_editor) {
scope.js_editor.resize();
scope.js_editor.renderer.updateFull();
}
- };
+ }
scope.jsEditorOptions = {
useWrapMode: true,
@@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
scope.validate = function () {
try {
var toValidate = new Function(scope.functionArgsString, scope.functionBody);
+ if (scope.noValidate) {
+ return true;
+ }
var res;
var validationError;
for (var i=0;i<scope.validationArgs.length;i++) {
@@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
}
};
- scope.$on('form-submit', function () {
- scope.functionValid = scope.validate();
- scope.updateValidity();
+ scope.$on('form-submit', function (event, args) {
+ if (!args || scope.validationTriggerArg && scope.validationTriggerArg == args) {
+ scope.validationArgs = scope.$eval(attrs.validationArgs);
+ scope.cleanupJsErrors();
+ scope.functionValid = true;
+ scope.updateValidity();
+ scope.functionValid = scope.validate();
+ scope.updateValidity();
+ }
+ });
+
+ scope.$on('update-ace-editor-size', function () {
+ updateEditorSize();
});
$compile(element.contents())(scope);
@@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
return {
restrict: "E",
require: "^ngModel",
- scope: {},
+ scope: {
+ disabled:'=ngDisabled',
+ noValidate: '=?',
+ fillHeight:'=?'
+ },
link: linker
};
}
ui/src/app/components/js-func.scss 10(+9 -1)
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
index 2bd5df1..e1072be 100644
--- a/ui/src/app/components/js-func.scss
+++ b/ui/src/app/components/js-func.scss
@@ -15,6 +15,12 @@
*/
tb-js-func {
position: relative;
+ .tb-disabled {
+ color: rgba(0,0,0,0.38);
+ }
+ .fill-height {
+ height: 100%;
+ }
}
.tb-js-func-panel {
@@ -23,8 +29,10 @@ tb-js-func {
height: 100%;
#tb-javascript-input {
min-width: 200px;
- min-height: 200px;
width: 100%;
height: 100%;
+ &:not(.fill-height) {
+ min-height: 200px;
+ }
}
}
ui/src/app/components/js-func.tpl.html 15(+8 -7)
diff --git a/ui/src/app/components/js-func.tpl.html b/ui/src/app/components/js-func.tpl.html
index 806de4a..93043d4 100644
--- a/ui/src/app/components/js-func.tpl.html
+++ b/ui/src/app/components/js-func.tpl.html
@@ -15,19 +15,20 @@
limitations under the License.
-->
-<div style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
+<div style="background: #fff;" ng-class="{'tb-disabled': disabled, 'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
<div layout="row" layout-align="start center" style="height: 40px;">
- <span style="font-style: italic;">function({{ functionArgsString }}) {</span>
+ <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
<span flex></span>
<div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div>
</div>
<div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column">
- <div flex id="tb-javascript-input"
- ui-ace="jsEditorOptions"
+ <div flex id="tb-javascript-input" ng-class="{'fill-height': fillHeight}"
+ ui-ace="jsEditorOptions"
+ ng-readonly="disabled"
ng-model="functionBody">
</div>
</div>
<div layout="row" layout-align="start center" style="height: 40px;">
- <span style="font-style: italic;">}</span>
- </div>
-</div>
\ No newline at end of file
+ <label class="tb-title no-padding">}</label>
+ </div>
+</div>
ui/src/app/locale/locale.constant.js 4(+3 -1)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 5dce787..b511b56 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', [])
"type-action": "Action",
"type-action-details": "Perform special action",
"type-rule-chain": "Rule Chain",
- "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain"
+ "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
+ "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
+ "ui-resources-load-error": "Failed to load configuration ui resources."
},
"rule-plugin": {
"management": "Rules and plugins management"
ui/src/app/rulechain/index.js 4(+4 -0)
diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js
index 7306762..c674467 100644
--- a/ui/src/app/rulechain/index.js
+++ b/ui/src/app/rulechain/index.js
@@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes';
import RuleChainsController from './rulechains.controller';
import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller';
import RuleChainDirective from './rulechain.directive';
+import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive';
+import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive';
@@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', [])
.controller('AddRuleNodeController', AddRuleNodeController)
.controller('AddRuleNodeLinkController', AddRuleNodeLinkController)
.directive('tbRuleChain', RuleChainDirective)
+ .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective)
+ .directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective)
.name;
ui/src/app/rulechain/rulechain.controller.js 21(+12 -9)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index 4eba5b2..dd48bb0 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
};
vm.saveRuleNode = function(theForm) {
- theForm.$setPristine();
- vm.isEditingRuleNode = false;
- vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
- vm.editingRuleNode = angular.copy(vm.editingRuleNode);
+ $scope.$broadcast('form-submit');
+ if (theForm.$valid) {
+ theForm.$setPristine();
+ vm.isEditingRuleNode = false;
+ vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
+ vm.editingRuleNode = angular.copy(vm.editingRuleNode);
+ }
};
vm.saveRuleNodeLink = function(theForm) {
@@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model;
var node = {
- id: model.nodes.length,
+ id: 'node-lib-' + componentType + '-' + model.nodes.length,
component: ruleNodeComponent,
name: '',
nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
@@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
vm.ruleChainModel.nodes.push(
{
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
component: types.inputNodeComponent,
name: "",
nodeClass: types.ruleNodeType.INPUT.nodeClass,
@@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) {
var node = {
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id,
additionalInfo: ruleNode.additionalInfo,
configuration: ruleNode.configuration,
@@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) {
ruleChainNode = {
- id: vm.nextNodeID++,
+ id: 'rule-chain-node-' + vm.nextNodeID++,
additionalInfo: ruleChainConnection.additionalInfo,
targetRuleChainId: ruleChainConnection.targetRuleChainId.id,
x: ruleChainConnection.additionalInfo.layoutX,
@@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
fullscreen: true,
targetEvent: $event
}).then(function (ruleNode) {
- ruleNode.id = vm.nextNodeID++;
+ ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
ruleNode.connectors = [];
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
ruleNode.connectors.push(
ui/src/app/rulechain/rulenode.scss 6(+6 -0)
diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss
index febc637..0466673 100644
--- a/ui/src/app/rulechain/rulenode.scss
+++ b/ui/src/app/rulechain/rulenode.scss
@@ -19,4 +19,10 @@
height: 300px;
display: block;
}
+}
+
+.tb-rulenode-directive-error {
+ color: rgb(221,44,0);
+ font-size: 13px;
+ font-weight: 400;
}
\ No newline at end of file
diff --git a/ui/src/app/rulechain/rulenode-config.directive.js b/ui/src/app/rulechain/rulenode-config.directive.js
new file mode 100644
index 0000000..4b75c79
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-config.directive.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import ruleNodeConfigTemplate from './rulenode-config.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RuleNodeConfigDirective($compile, $templateCache, $injector, $translate) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(ruleNodeConfigTemplate);
+ element.html(template);
+
+ scope.$watch('configuration', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ ngModelCtrl.$setViewValue(scope.configuration);
+ }
+ });
+
+ ngModelCtrl.$render = function () {
+ scope.configuration = ngModelCtrl.$viewValue;
+ };
+
+ scope.useDefinedDirective = function() {
+ return scope.nodeDefinition.configDirective && !scope.definedDirectiveError;
+ };
+
+ validateDefinedDirective();
+
+ function validateDefinedDirective() {
+ if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) {
+ scope.definedDirectiveError = scope.nodeDefinition.uiResourceLoadError;
+ } else {
+ var definedDirective = scope.nodeDefinition.configDirective;
+ if (definedDirective && definedDirective.length) {
+ if (!$injector.has(definedDirective + 'Directive')) {
+ scope.definedDirectiveError = $translate.instant('rulenode.directive-is-not-loaded', {directiveName: definedDirective});
+ }
+ }
+ }
+ }
+
+ $compile(element.contents())(scope);
+ };
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ scope: {
+ nodeDefinition:'=',
+ required:'=ngRequired',
+ readonly:'=ngReadonly'
+ },
+ link: linker
+ };
+
+}
diff --git a/ui/src/app/rulechain/rulenode-config.tpl.html b/ui/src/app/rulechain/rulenode-config.tpl.html
new file mode 100644
index 0000000..32d5347
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-config.tpl.html
@@ -0,0 +1,32 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<tb-rule-node-defined-config ng-if="useDefinedDirective()"
+ ng-model="configuration"
+ rule-node-directive="{{nodeDefinition.configDirective}}"
+ ng-required="required"
+ ng-readonly="readonly">
+</tb-rule-node-defined-config>
+<div class="tb-rulenode-directive-error" ng-if="definedDirectiveError">{{definedDirectiveError}}</div>
+<tb-json-object-edit ng-if="!useDefinedDirective()"
+ class="tb-rule-node-configuration-json"
+ ng-model="configuration"
+ label="{{ 'rulenode.configuration' | translate }}"
+ ng-required="required"
+ fill-height="true">
+</tb-json-object-edit>
diff --git a/ui/src/app/rulechain/rulenode-defined-config.directive.js b/ui/src/app/rulechain/rulenode-defined-config.directive.js
new file mode 100644
index 0000000..5ec2620
--- /dev/null
+++ b/ui/src/app/rulechain/rulenode-defined-config.directive.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const SNAKE_CASE_REGEXP = /[A-Z]/g;
+
+/*@ngInject*/
+export default function RuleNodeDefinedConfigDirective($compile) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+
+ attrs.$observe('ruleNodeDirective', function() {
+ loadTemplate();
+ });
+
+ scope.$watch('configuration', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ ngModelCtrl.$setViewValue(scope.configuration);
+ }
+ });
+
+ ngModelCtrl.$render = function () {
+ scope.configuration = ngModelCtrl.$viewValue;
+ };
+
+ function loadTemplate() {
+ var directive = snake_case(attrs.ruleNodeDirective, '-');
+ var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`;
+ element.html(template);
+ $compile(element.contents())(scope);
+ }
+
+ function snake_case(name, separator) {
+ separator = separator || '_';
+ return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
+ };
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ scope: {
+ required:'=ngRequired',
+ readonly:'=ngReadonly'
+ },
+ link: linker
+ };
+
+}
diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
index 30cf075..ad109ef 100644
--- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html
+++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html
@@ -38,11 +38,16 @@
ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }}
</md-checkbox>
</md-input-container>
- <tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
+ <tb-rule-node-config ng-model="ruleNode.configuration"
+ ng-required="true"
+ node-definition="ruleNode.component.configurationDescriptor.nodeDefinition"
+ ng-readonly="$root.loading || !isEdit || isReadOnly">
+ </tb-rule-node-config>
+ <!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration"
label="{{ 'rulenode.configuration' | translate }}"
ng-required="true"
fill-height="true">
- </tb-json-object-edit>
+ </tb-json-object-edit-->
<md-input-container class="md-block">
<label translate>rulenode.description</label>
<textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea>