Enable/Disable Sandboxed JavaScript environment. UI: Tidy button to format java scripts.
This commit is contained in:
parent
af5791ab7a
commit
efbc65e11f
@ -20,10 +20,13 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import delight.nashornsandbox.NashornSandbox;
|
||||
import delight.nashornsandbox.NashornSandboxes;
|
||||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@ -35,7 +38,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
@Slf4j
|
||||
public abstract class AbstractNashornJsSandboxService implements JsSandboxService {
|
||||
|
||||
private NashornSandbox sandbox = NashornSandboxes.create();
|
||||
private NashornSandbox sandbox;
|
||||
private ScriptEngine engine;
|
||||
private ExecutorService monitorExecutorService;
|
||||
|
||||
private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
|
||||
@ -44,11 +48,17 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
|
||||
sandbox.setExecutor(monitorExecutorService);
|
||||
sandbox.setMaxCPUTime(getMaxCpuTime());
|
||||
sandbox.allowNoBraces(false);
|
||||
sandbox.setMaxPreparedStatements(30);
|
||||
if (useJsSandbox()) {
|
||||
sandbox = NashornSandboxes.create();
|
||||
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
|
||||
sandbox.setExecutor(monitorExecutorService);
|
||||
sandbox.setMaxCPUTime(getMaxCpuTime());
|
||||
sandbox.allowNoBraces(false);
|
||||
sandbox.setMaxPreparedStatements(30);
|
||||
} else {
|
||||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
|
||||
engine = factory.getScriptEngine(new String[]{"--no-java"});
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@ -58,6 +68,8 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean useJsSandbox();
|
||||
|
||||
protected abstract int getMonitorThreadPoolSize();
|
||||
|
||||
protected abstract long getMaxCpuTime();
|
||||
@ -70,7 +82,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
|
||||
String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
|
||||
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
|
||||
try {
|
||||
sandbox.eval(jsScript);
|
||||
if (useJsSandbox()) {
|
||||
sandbox.eval(jsScript);
|
||||
} else {
|
||||
engine.eval(jsScript);
|
||||
}
|
||||
functionsMap.put(scriptId, functionName);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
|
||||
@ -87,7 +103,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
|
||||
}
|
||||
if (!isBlackListed(scriptId)) {
|
||||
try {
|
||||
return Futures.immediateFuture(sandbox.getSandboxedInvocable().invokeFunction(functionName, args));
|
||||
Object result;
|
||||
if (useJsSandbox()) {
|
||||
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
|
||||
} else {
|
||||
result = ((Invocable)engine).invokeFunction(functionName, args);
|
||||
}
|
||||
return Futures.immediateFuture(result);
|
||||
} catch (Exception e) {
|
||||
blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
|
||||
return Futures.immediateFailedFuture(e);
|
||||
@ -103,7 +125,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
|
||||
String functionName = functionsMap.get(scriptId);
|
||||
if (functionName != null) {
|
||||
try {
|
||||
sandbox.eval(functionName + " = undefined;");
|
||||
if (useJsSandbox()) {
|
||||
sandbox.eval(functionName + " = undefined;");
|
||||
} else {
|
||||
engine.eval(functionName + " = undefined;");
|
||||
}
|
||||
functionsMap.remove(scriptId);
|
||||
blackListedFunctions.remove(scriptId);
|
||||
} catch (ScriptException e) {
|
||||
|
||||
@ -24,6 +24,9 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
|
||||
|
||||
@Value("${actors.rule.js_sandbox.use_js_sandbox}")
|
||||
private boolean useJsSandbox;
|
||||
|
||||
@Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
|
||||
private int monitorThreadPoolSize;
|
||||
|
||||
@ -33,6 +36,11 @@ public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
|
||||
@Value("${actors.rule.js_sandbox.max_errors}")
|
||||
private int maxErrors;
|
||||
|
||||
@Override
|
||||
protected boolean useJsSandbox() {
|
||||
return useJsSandbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMonitorThreadPoolSize() {
|
||||
return monitorThreadPoolSize;
|
||||
|
||||
@ -239,6 +239,8 @@ actors:
|
||||
# Specify thread pool size for external call service
|
||||
external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
|
||||
js_sandbox:
|
||||
# Use Sandboxed (secured) JavaScript environment
|
||||
use_js_sandbox: "${ACTORS_RULE_JS_SANDBOX_USE_JS_SANDBOX:true}"
|
||||
# Specify thread pool size for JavaScript sandbox resource monitor
|
||||
monitor_thread_pool_size: "${ACTORS_RULE_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
|
||||
# Maximum CPU time in milliseconds allowed for script execution
|
||||
|
||||
@ -37,7 +37,7 @@ public class RuleNodeJsScriptEngineTest {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws Exception {
|
||||
jsSandboxService = new TestNashornJsSandboxService(1, 100, 3);
|
||||
jsSandboxService = new TestNashornJsSandboxService(false, 1, 100, 3);
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@ -27,17 +27,24 @@ import java.util.concurrent.Executors;
|
||||
|
||||
public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService {
|
||||
|
||||
private boolean useJsSandbox;
|
||||
private final int monitorThreadPoolSize;
|
||||
private final long maxCpuTime;
|
||||
private final int maxErrors;
|
||||
|
||||
public TestNashornJsSandboxService(int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
|
||||
public TestNashornJsSandboxService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
|
||||
this.useJsSandbox = useJsSandbox;
|
||||
this.monitorThreadPoolSize = monitorThreadPoolSize;
|
||||
this.maxCpuTime = maxCpuTime;
|
||||
this.maxErrors = maxErrors;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useJsSandbox() {
|
||||
return useJsSandbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMonitorThreadPoolSize() {
|
||||
return monitorThreadPoolSize;
|
||||
|
||||
@ -30,6 +30,10 @@ import jsFuncTemplate from './js-func.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
import beautify from 'js-beautify';
|
||||
|
||||
const js_beautify = beautify.js;
|
||||
|
||||
/* eslint-disable angular/angularelement */
|
||||
|
||||
export default angular.module('thingsboard.directives.jsFunc', [thingsboardToast, thingsboardUtils, thingsboardExpandFullscreen])
|
||||
@ -72,6 +76,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
|
||||
updateEditorSize();
|
||||
};
|
||||
|
||||
scope.beautifyJs = function () {
|
||||
var res = js_beautify(scope.functionBody, {indent_size: 4, wrap_line_length: 60});
|
||||
scope.functionBody = res;
|
||||
};
|
||||
|
||||
function updateEditorSize() {
|
||||
if (scope.js_editor) {
|
||||
scope.js_editor.resize();
|
||||
|
||||
@ -23,6 +23,19 @@ tb-js-func {
|
||||
}
|
||||
}
|
||||
|
||||
.tb-js-func-toolbar {
|
||||
.md-button.tidy {
|
||||
color: #7B7B7B;
|
||||
min-width: 32px;
|
||||
min-height: 15px;
|
||||
line-height: 15px;
|
||||
font-size: 0.800rem;
|
||||
margin: 0 5px 0 0;
|
||||
padding: 4px;
|
||||
background: rgba(220, 220, 220, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
.tb-js-func-panel {
|
||||
margin-left: 15px;
|
||||
border: 1px solid #C0C0C0;
|
||||
|
||||
@ -16,9 +16,12 @@
|
||||
|
||||
-->
|
||||
<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()">
|
||||
<div layout="row" layout-align="start center" style="height: 40px;">
|
||||
<div layout="row" layout-align="start center" style="height: 40px;" class="tb-js-func-toolbar">
|
||||
<label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label>
|
||||
<span flex></span>
|
||||
<md-button ng-if="!disabled" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJs()">{{
|
||||
'js-func.tidy' | translate }}
|
||||
</md-button>
|
||||
<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 id="tb-javascript-panel" class="tb-js-func-panel">
|
||||
|
||||
@ -29,6 +29,10 @@ import jsonContentTemplate from './json-content.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
import beautify from 'js-beautify';
|
||||
|
||||
const js_beautify = beautify.js;
|
||||
|
||||
export default angular.module('thingsboard.directives.jsonContent', [])
|
||||
.directive('tbJsonContent', JsonContent)
|
||||
.name;
|
||||
@ -52,6 +56,11 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
|
||||
updateEditorSize();
|
||||
};
|
||||
|
||||
scope.beautifyJson = function () {
|
||||
var res = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60});
|
||||
scope.contentBody = res;
|
||||
};
|
||||
|
||||
function updateEditorSize() {
|
||||
if (scope.json_editor) {
|
||||
scope.json_editor.resize();
|
||||
|
||||
@ -20,6 +20,19 @@ tb-json-content {
|
||||
}
|
||||
}
|
||||
|
||||
.tb-json-content-toolbar {
|
||||
.md-button.tidy {
|
||||
color: #7B7B7B;
|
||||
min-width: 32px;
|
||||
min-height: 15px;
|
||||
line-height: 15px;
|
||||
font-size: 0.800rem;
|
||||
margin: 0 5px 0 0;
|
||||
padding: 4px;
|
||||
background: rgba(220, 220, 220, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
.tb-json-content-panel {
|
||||
margin-left: 15px;
|
||||
border: 1px solid #C0C0C0;
|
||||
|
||||
@ -16,9 +16,12 @@
|
||||
|
||||
-->
|
||||
<div style="background: #fff;" ng-class="{'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">
|
||||
<div layout="row" layout-align="start center" style="height: 40px;" class="tb-json-content-toolbar">
|
||||
<label class="tb-title no-padding">{{ label }}</label>
|
||||
<span flex></span>
|
||||
<md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJson()">{{
|
||||
'js-func.tidy' | translate }}
|
||||
</md-button>
|
||||
<md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
|
||||
</div>
|
||||
<div flex id="tb-json-panel" class="tb-json-content-panel" layout="column">
|
||||
|
||||
@ -991,7 +991,8 @@ export default angular.module('thingsboard.locale', [])
|
||||
},
|
||||
"js-func": {
|
||||
"no-return-error": "Function must return value!",
|
||||
"return-type-mismatch": "Function must return value of '{{type}}' type!"
|
||||
"return-type-mismatch": "Function must return value of '{{type}}' type!",
|
||||
"tidy": "Tidy"
|
||||
},
|
||||
"key-val": {
|
||||
"key": "Key",
|
||||
|
||||
@ -76,9 +76,12 @@ md-dialog.tb-node-script-test-dialog {
|
||||
position: absolute;
|
||||
font-size: 0.800rem;
|
||||
font-weight: 500;
|
||||
top: 10px;
|
||||
top: 13px;
|
||||
right: 40px;
|
||||
z-index: 5;
|
||||
&.tb-js-function {
|
||||
right: 80px;
|
||||
}
|
||||
label {
|
||||
color: #00acc1;
|
||||
background: rgba(220, 220, 220, 0.35);
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
<div id="bottom_panel" class="tb-split tb-split-vertical">
|
||||
<div id="bottom_left_panel" class="tb-split tb-content">
|
||||
<div class="tb-resize-container">
|
||||
<div class="tb-editor-area-title-panel">
|
||||
<div class="tb-editor-area-title-panel tb-js-function">
|
||||
<label>{{ vm.functionTitle }}</label>
|
||||
</div>
|
||||
<ng-form name="funcBodyForm">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user