diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index c6befdd8ca..f69ad819ba 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -15,9 +15,17 @@ */ package org.thingsboard.server.controller; +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; +import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.PluginId; @@ -30,17 +38,25 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.exception.ThingsboardException; +import org.thingsboard.server.service.script.NashornJsEngine; import java.util.List; +import java.util.Map; +import java.util.Set; +@Slf4j @RestController @RequestMapping("/api") public class RuleChainController extends BaseController { public static final String RULE_CHAIN_ID = "ruleChainId"; + private static final ObjectMapper objectMapper = new ObjectMapper(); + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET) @ResponseBody @@ -203,4 +219,71 @@ public class RuleChainController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST) + @ResponseBody + public JsonNode testScript(@RequestBody JsonNode inputParams) throws ThingsboardException { + try { + String script = inputParams.get("script").asText(); + String scriptType = inputParams.get("scriptType").asText(); + String functionName = inputParams.get("functionName").asText(); + JsonNode argNamesJson = inputParams.get("argNames"); + String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class); + + String data = inputParams.get("msg").asText(); + JsonNode metadataJson = inputParams.get("metadata"); + Map metadata = objectMapper.convertValue(metadataJson, new TypeReference>() {}); + String msgType = inputParams.get("msgType").asText(); + String output = ""; + String errorText = ""; + ScriptEngine engine = null; + try { + engine = new NashornJsEngine(script, functionName, argNames); + TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data); + switch (scriptType) { + case "update": + output = msgToOutput(engine.executeUpdate(inMsg)); + break; + case "generate": + output = msgToOutput(engine.executeGenerate(inMsg)); + break; + case "filter": + boolean result = engine.executeFilter(inMsg); + output = Boolean.toString(result); + break; + case "switch": + Set states = engine.executeSwitch(inMsg); + output = objectMapper.writeValueAsString(states); + break; + default: + throw new IllegalArgumentException("Unsupported script type: " + scriptType); + } + } catch (Exception e) { + log.error("Error evaluating JS function", e); + errorText = e.getMessage(); + } finally { + if (engine != null) { + engine.destroy(); + } + } + ObjectNode result = objectMapper.createObjectNode(); + result.put("output", output); + result.put("error", errorText); + return result; + } catch (Exception e) { + throw handleException(e); + } + } + + private String msgToOutput(TbMsg msg) throws Exception { + ObjectNode msgData = objectMapper.createObjectNode(); + if (!StringUtils.isEmpty(msg.getData())) { + msgData.set("msg", objectMapper.readTree(msg.getData())); + } + Map metadata = msg.getMetaData().getData(); + msgData.set("metadata", objectMapper.valueToTree(metadata)); + msgData.put("msgType", msg.getType()); + return objectMapper.writeValueAsString(msgData); + } + } 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 index 79006c2c72..cc5a4811d2 100644 --- 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 @@ -1,2 +1,2 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(34)},function(e,t){},1,1,function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
"},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},6,function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
{{ \'tb.rulenode.clone-message\' | translate }}
'},function(e,t){e.exports="
{{ 'tb.rulenode.clone-message' | translate }}
"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.originator=null,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue,n.configuration.originatorId&&n.configuration.originatorType?n.originator={id:n.configuration.originatorId,entityType:n.configuration.originatorType}:n.originator=null,n.$watch("originator",function(e,t){angular.equals(e,t)||(n.originator?(i.$viewValue.originatorId=n.originator.id,i.$viewValue.originatorType=n.originator.entityType):(i.$viewValue.originatorId=null,i.$viewValue.originatorType=null))},!0)},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(4),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(19),i=r(a),o=n(17),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(22),i=r(a),o=n(23),l=r(o),s=n(20),u=r(s),d=n(24),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(27),i=r(a),o=n(26),l=r(o),s=n(28),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(31),i=r(a),o=n(33),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).name},[38,16],function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(37),i=r(a),o=n(25),l=r(o),s=n(21),u=r(s),d=n(32),c=r(d),m=n(18),g=r(m),f=n(30),p=r(f),b=n(29),v=r(b),y=n(36),h=r(y);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",v.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};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 a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(35),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name},function(e,t,n,r){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,r,a){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var o=n(r),l=a(o)}])); +!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(34)},function(e,t){},1,1,function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},6,function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
{{ \'tb.rulenode.clone-message\' | translate }}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
{{ 'tb.rulenode.clone-message' | translate }}
"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(4),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(19),i=r(a),o=n(17),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(22),i=r(a),o=n(23),l=r(o),s=n(20),u=r(s),d=n(24),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(27),i=r(a),o=n(26),l=r(o),s=n(28),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(31),i=r(a),o=n(33),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(37),i=r(a),o=n(25),l=r(o),s=n(21),u=r(s),d=n(32),c=r(d),m=n(18),g=r(m),f=n(30),p=r(f),b=n(29),v=r(b),y=n(36),h=r(y);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",v.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};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 a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(35),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index b930cf18fa..03b3a82684 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -33,7 +33,8 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co getRuleNodeComponents: getRuleNodeComponents, getRuleNodeComponentByClazz: getRuleNodeComponentByClazz, getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, - resolveTargetRuleChains: resolveTargetRuleChains + resolveTargetRuleChains: resolveTargetRuleChains, + testScript: testScript }; return service; @@ -292,5 +293,15 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes); } + function testScript(inputParams) { + var deferred = $q.defer(); + var url = '/api/ruleChain/testScript'; + $http.post(url, inputParams).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } } diff --git a/ui/src/app/components/kv-map.directive.js b/ui/src/app/components/kv-map.directive.js new file mode 100644 index 0000000000..bc8865f4b1 --- /dev/null +++ b/ui/src/app/components/kv-map.directive.js @@ -0,0 +1,119 @@ +/* + * 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. + */ +import './kv-map.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import kvMapTemplate from './kv-map.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.directives.keyValMap', []) + .directive('tbKeyValMap', KeyValMap) + .name; + +/*@ngInject*/ +function KeyValMap() { + return { + restrict: "E", + scope: true, + bindToController: { + disabled:'=ngDisabled', + titleText: '@?', + keyPlaceholderText: '@?', + valuePlaceholderText: '@?', + noDataText: '@?', + keyValMap: '=' + }, + controller: KeyValMapController, + controllerAs: 'vm', + templateUrl: kvMapTemplate + }; +} + +/*@ngInject*/ +function KeyValMapController($scope, $mdUtil) { + + let vm = this; + + vm.kvList = []; + + vm.removeKeyVal = removeKeyVal; + vm.addKeyVal = addKeyVal; + + $scope.$watch('vm.keyValMap', () => { + stopWatchKvList(); + vm.kvList.length = 0; + if (vm.keyValMap) { + for (var property in vm.keyValMap) { + if (vm.keyValMap.hasOwnProperty(property)) { + vm.kvList.push( + { + key: property + '', + value: vm.keyValMap[property] + } + ); + } + } + } + $mdUtil.nextTick(() => { + watchKvList(); + }); + }); + + function watchKvList() { + $scope.kvListWatcher = $scope.$watch('vm.kvList', () => { + if (!vm.keyValMap) { + return; + } + for (var property in vm.keyValMap) { + if (vm.keyValMap.hasOwnProperty(property)) { + delete vm.keyValMap[property]; + } + } + for (var i=0;i -1) { + vm.kvList.splice(index, 1); + } + } + + function addKeyVal() { + if (!vm.kvList) { + vm.kvList = []; + } + vm.kvList.push( + { + key: '', + value: '' + } + ); + } +} diff --git a/ui/src/app/components/kv-map.scss b/ui/src/app/components/kv-map.scss new file mode 100644 index 0000000000..d51653c3b8 --- /dev/null +++ b/ui/src/app/components/kv-map.scss @@ -0,0 +1,26 @@ +/** + * 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-kv-map { + span.no-data-found { + position: relative; + height: 40px; + text-transform: uppercase; + display: flex; + &.disabled { + color: rgba(0,0,0,0.38); + } + } +} \ No newline at end of file diff --git a/ui/src/app/components/kv-map.tpl.html b/ui/src/app/components/kv-map.tpl.html new file mode 100644 index 0000000000..5114256713 --- /dev/null +++ b/ui/src/app/components/kv-map.tpl.html @@ -0,0 +1,58 @@ + +
+ +
+ + + + + + + + + {{ 'key-val.remove-entry' | translate }} + + + close + + +
+ {{vm.noDataText ? vm.noDataText : 'key-val.no-data'}} +
+ + + {{ 'key-val.add-entry' | translate }} + + action.add + +
+
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index 6cb9bbd279..6ec7ef79a9 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive'; import thingsboardOnFinishRender from '../components/finish-render.directive'; import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; +import thingsboardKvMap from '../components/kv-map.directive'; import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; import thingsboardJsonContent from '../components/json-content.directive'; @@ -93,6 +94,7 @@ export default angular.module('thingsboard.home', [ thingsboardOnFinishRender, thingsboardSideMenu, thingsboardDashboardAutocomplete, + thingsboardKvMap, thingsboardJsonObjectEdit, thingsboardJsonContent ]) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index f3666447dc..9d35cd137e 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -962,6 +962,13 @@ export default angular.module('thingsboard.locale', []) "no-return-error": "Function must return value!", "return-type-mismatch": "Function must return value of '{{type}}' type!" }, + "key-val": { + "key": "Key", + "value": "Value", + "remove-entry": "Remove entry", + "add-entry": "Add entry", + "no-data": "No entries" + }, "layout": { "layout": "Layout", "manage": "Manage layouts", @@ -1220,7 +1227,15 @@ export default angular.module('thingsboard.locale', []) "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.", - "invalid-target-rulechain": "Unable to resolve target rule chain!" + "invalid-target-rulechain": "Unable to resolve target rule chain!", + "test-script-function": "Test script function", + "message": "Message", + "message-type": "Message type", + "message-type-required": "Message type is required", + "metadata": "Metadata", + "metadata-required": "Metadata entries can't be empty.", + "output": "Output", + "test": "Test" }, "rule-plugin": { "management": "Rules and plugins management" diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js index c674467d35..7740dd0669 100644 --- a/ui/src/app/rulechain/index.js +++ b/ui/src/app/rulechain/index.js @@ -17,11 +17,13 @@ import RuleChainRoutes from './rulechain.routes'; import RuleChainsController from './rulechains.controller'; import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; +import NodeScriptTestController from './script/node-script-test.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'; +import NodeScriptTest from './script/node-script-test.service'; export default angular.module('thingsboard.ruleChain', []) .config(RuleChainRoutes) @@ -29,9 +31,11 @@ export default angular.module('thingsboard.ruleChain', []) .controller('RuleChainController', RuleChainController) .controller('AddRuleNodeController', AddRuleNodeController) .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) + .controller('NodeScriptTestController', NodeScriptTestController) .directive('tbRuleChain', RuleChainDirective) .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective) .directive('tbRuleNodeConfig', RuleNodeConfigDirective) .directive('tbRuleNode', RuleNodeDirective) .directive('tbRuleNodeLink', LinkDirective) + .factory('ruleNodeScriptTest', NodeScriptTest) .name; diff --git a/ui/src/app/rulechain/script/node-script-test.controller.js b/ui/src/app/rulechain/script/node-script-test.controller.js new file mode 100644 index 0000000000..487d11ff19 --- /dev/null +++ b/ui/src/app/rulechain/script/node-script-test.controller.js @@ -0,0 +1,177 @@ +/* + * 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. + */ + +import './node-script-test.scss'; + +import Split from 'split.js'; + +import beautify from 'js-beautify'; + +const js_beautify = beautify.js; + +/*@ngInject*/ +export default function NodeScriptTestController($scope, $mdDialog, $window, $document, $timeout, + $q, $mdUtil, $translate, toast, types, utils, + ruleChainService, onShowingCallback, msg, msgType, metadata, + functionTitle, inputParams) { + + var vm = this; + + vm.types = types; + vm.functionTitle = functionTitle; + vm.inputParams = inputParams; + vm.inputParams.msg = js_beautify(angular.toJson(msg), {indent_size: 4}); + vm.inputParams.metadata = metadata; + vm.inputParams.msgType = msgType; + + vm.output = ''; + + vm.test = test; + vm.save = save; + vm.cancel = cancel; + + $scope.$watch('theForm.metadataForm.$dirty', (newVal) => { + if (newVal) { + toast.hide(); + } + }); + + onShowingCallback.onShowed = () => { + vm.nodeScriptTestDialogElement = angular.element('.tb-node-script-test-dialog'); + var w = vm.nodeScriptTestDialogElement.width(); + if (w > 0) { + initSplitLayout(); + } else { + $scope.$watch( + function () { + return vm.nodeScriptTestDialogElement[0].offsetWidth || parseInt(vm.nodeScriptTestDialogElement.css('width'), 10); + }, + function (newSize) { + if (newSize > 0) { + initSplitLayout(); + } + } + ); + } + }; + + function onDividerDrag() { + $scope.$broadcast('update-ace-editor-size'); + } + + function initSplitLayout() { + if (!vm.layoutInited) { + Split([angular.element('#top_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#bottom_panel', vm.nodeScriptTestDialogElement)[0]], { + sizes: [35, 65], + gutterSize: 8, + cursor: 'row-resize', + direction: 'vertical', + onDrag: function () { + onDividerDrag() + } + }); + + Split([angular.element('#top_left_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#top_right_panel', vm.nodeScriptTestDialogElement)[0]], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize', + onDrag: function () { + onDividerDrag() + } + }); + + Split([angular.element('#bottom_left_panel', vm.nodeScriptTestDialogElement)[0], angular.element('#bottom_right_panel', vm.nodeScriptTestDialogElement)[0]], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize', + onDrag: function () { + onDividerDrag() + } + }); + + onDividerDrag(); + + $scope.$applyAsync(function () { + vm.layoutInited = true; + var w = angular.element($window); + $timeout(function () { + w.triggerHandler('resize') + }); + }); + + } + } + + function test() { + testNodeScript().then( + (output) => { + vm.output = js_beautify(output, {indent_size: 4}); + } + ); + } + + function checkInputParamErrors() { + $scope.theForm.metadataForm.$setPristine(); + $scope.$broadcast('form-submit', 'validatePayload'); + if (!$scope.theForm.payloadForm.$valid) { + return false; + } else if (!$scope.theForm.metadataForm.$valid) { + showMetadataError($translate.instant('rulenode.metadata-required')); + return false; + } + return true; + } + + function showMetadataError(error) { + var toastParent = angular.element('#metadata-panel', vm.nodeScriptTestDialogElement); + toast.showError(error, toastParent, 'bottom left'); + } + + function testNodeScript() { + var deferred = $q.defer(); + if (checkInputParamErrors()) { + $mdUtil.nextTick(() => { + ruleChainService.testScript(vm.inputParams).then( + (result) => { + if (result.error) { + toast.showError(result.error); + deferred.reject(); + } else { + deferred.resolve(result.output); + } + }, + () => { + deferred.reject(); + } + ); + }); + } else { + deferred.reject(); + } + return deferred.promise; + } + + function cancel() { + $mdDialog.cancel(); + } + + function save() { + testNodeScript().then(() => { + $scope.theForm.funcBodyForm.$setPristine(); + $mdDialog.hide(vm.inputParams.script); + }); + } +} diff --git a/ui/src/app/rulechain/script/node-script-test.scss b/ui/src/app/rulechain/script/node-script-test.scss new file mode 100644 index 0000000000..50bd4964d3 --- /dev/null +++ b/ui/src/app/rulechain/script/node-script-test.scss @@ -0,0 +1,108 @@ +/** + * 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. + */ +@import '../../../scss/constants'; +@import "~compass-sass-mixins/lib/compass"; + +md-dialog.tb-node-script-test-dialog { + &.md-dialog-fullscreen { + min-height: 100%; + min-width: 100%; + border-radius: 0; + } + + .tb-split { + @include box-sizing(border-box); + overflow-y: auto; + overflow-x: hidden; + } + + .ace_editor { + font-size: 14px !important; + } + + .tb-content { + border: 1px solid #C0C0C0; + padding-top: 5px; + padding-left: 5px; + } + + .gutter { + background-color: #eeeeee; + + background-repeat: no-repeat; + background-position: 50%; + } + + .gutter.gutter-horizontal { + cursor: col-resize; + background-image: url('../../../../node_modules/split.js/grips/vertical.png'); + } + + .gutter.gutter-vertical { + cursor: row-resize; + background-image: url('../../../../node_modules/split.js/grips/horizontal.png'); + } + + .tb-split.tb-split-horizontal, .gutter.gutter-horizontal { + height: 100%; + float: left; + } + + .tb-split.tb-split-vertical { + display: flex; + .tb-split.tb-content { + height: 100%; + } + } + + div.tb-editor-area-title-panel { + position: absolute; + font-size: 0.800rem; + font-weight: 500; + top: 10px; + right: 40px; + z-index: 5; + label { + color: #00acc1; + background: rgba(220, 220, 220, 0.35); + border-radius: 5px; + padding: 4px; + text-transform: uppercase; + } + .md-button { + color: #7B7B7B; + min-width: 32px; + min-height: 15px; + line-height: 15px; + font-size: 0.800rem; + margin: 0; + padding: 4px; + background: rgba(220, 220, 220, 0.35); + } + } + + .tb-resize-container { + overflow-y: auto; + height: 100%; + width: 100%; + position: relative; + + .ace_editor { + height: 100%; + } + } + +} diff --git a/ui/src/app/rulechain/script/node-script-test.service.js b/ui/src/app/rulechain/script/node-script-test.service.js new file mode 100644 index 0000000000..c685634cf5 --- /dev/null +++ b/ui/src/app/rulechain/script/node-script-test.service.js @@ -0,0 +1,81 @@ +/* + * 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 nodeScriptTestTemplate from './node-script-test.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function NodeScriptTest($q, $mdDialog, $document) { + + var service = { + testNodeScript: testNodeScript + }; + + return service; + + function testNodeScript($event, script, scriptType, functionTitle, functionName, argNames, msg, metadata, msgType) { + var deferred = $q.defer(); + + if ($event) { + $event.stopPropagation(); + } + var onShowingCallback = { + onShowed: () => { + } + }; + + var inputParams = { + script: script, + scriptType: scriptType, + functionName: functionName, + argNames: argNames + }; + + $mdDialog.show({ + controller: 'NodeScriptTestController', + controllerAs: 'vm', + templateUrl: nodeScriptTestTemplate, + parent: angular.element($document[0].body), + locals: { + msg: msg, + metadata: metadata, + msgType: msgType, + functionTitle: functionTitle, + inputParams: inputParams, + onShowingCallback: onShowingCallback + }, + fullscreen: true, + skipHide: true, + targetEvent: $event, + onComplete: () => { + onShowingCallback.onShowed(); + } + }).then( + (script) => { + deferred.resolve(script); + }, + () => { + deferred.reject(); + } + ); + + return deferred.promise; + } + +} \ No newline at end of file diff --git a/ui/src/app/rulechain/script/node-script-test.tpl.html b/ui/src/app/rulechain/script/node-script-test.tpl.html new file mode 100644 index 0000000000..933724061f --- /dev/null +++ b/ui/src/app/rulechain/script/node-script-test.tpl.html @@ -0,0 +1,119 @@ + + +
+ +
+

{{ 'rulenode.test-script-function' | translate }}

+ + + + +
+
+ +
+
+
+
+
+ +
+ +
+
+ + + +
+
rulenode.message-type-required
+
+
+
+ + +
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+ +
+ + + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+ + + {{ 'rulenode.test' | translate }} + + + + {{ 'action.save' | translate }} + + {{ 'action.cancel' | translate }} + +
+