脚本管理/执行脚本

This commit is contained in:
mxd 2020-04-19 18:53:05 +08:00
parent f26a2660e8
commit c5e0b94ca3
28 changed files with 1504 additions and 4 deletions

14
pom.xml
View File

@ -22,8 +22,9 @@
<alibaba.druid.version>1.1.16</alibaba.druid.version>
<alibaba.transmittable.version>2.11.2</alibaba.transmittable.version>
<mybatis.plus.version>3.1.0</mybatis.plus.version>
<apache.commons.text.verion>1.6</apache.commons.text.verion>
<apache.commons.csv.verion>1.8</apache.commons.csv.verion>
<apache.commons.text.version>1.6</apache.commons.text.version>
<apache.commons.csv.version>1.8</apache.commons.csv.version>
<apache.commons.exec.version>1.3</apache.commons.exec.version>
<commons.io.version>2.6</commons.io.version>
<guava.version>28.2-jre</guava.version>
<jsoup.version>1.11.3</jsoup.version>
@ -82,12 +83,17 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${apache.commons.text.verion}</version>
<version>${apache.commons.text.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${apache.commons.csv.verion}</version>
<version>${apache.commons.csv.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${apache.commons.exec.version}</version>
</dependency>
<!-- commons包 -->
<dependency>

View File

@ -0,0 +1,81 @@
package org.spiderflow.core.executor.shape;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spiderflow.context.SpiderContext;
import org.spiderflow.core.io.ScriptResponse;
import org.spiderflow.core.service.ScriptService;
import org.spiderflow.core.utils.ExpressionUtils;
import org.spiderflow.executor.ShapeExecutor;
import org.spiderflow.model.SpiderNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Map;
@Component
public class ScriptExecutor implements ShapeExecutor {
public static final String RESULT_VARIABLE = "scriptVariable";
public static final String SCRIPT_TIMEOUT = "scriptTimeout";
public static final String SCRIPT_NAME = "scriptName";
public static final String SCRIPT_FILE = "scriptFile";
public static final String SCRIPT_CHARSET = "scriptCharset";
public static final String SCRIPT_PARAMETER = "scriptParameter";
private static Logger logger = LoggerFactory.getLogger(ScriptExecutor.class);
@Autowired
private ScriptService scriptService;
@Override
public String supportShape() {
return "script";
}
@Override
public void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {
String scriptName = node.getStringJsonValue(SCRIPT_NAME);
String scriptFile = node.getStringJsonValue(SCRIPT_FILE);
String parameter = node.getStringJsonValue(SCRIPT_PARAMETER);
int timeout = NumberUtils.toInt(node.getStringJsonValue(SCRIPT_TIMEOUT,"-1"),-1);
if(StringUtils.isBlank(scriptName) || StringUtils.isBlank(scriptFile)){
logger.info("脚本名和脚本文件名不能为空!");
return;
}
File file = scriptService.getScriptFile(scriptName, scriptFile);
if(!file.exists()){
logger.info("在脚本{}中找不到脚本文件{}",scriptName,scriptFile);
return;
}
String charset = node.getStringJsonValue(SCRIPT_CHARSET);
String variable = node.getStringJsonValue(RESULT_VARIABLE);
if(parameter != null){
try {
Object value = ExpressionUtils.execute(parameter,variables);
logger.info("脚本参数:{}",value);
if(value != null){
parameter = value.toString();
}
} catch (Exception e) {
logger.error("获取脚本参数出错",e);
}
}
ScriptResponse response = scriptService.execute(scriptName, scriptFile, parameter, timeout);
if(StringUtils.isNotBlank(charset)){
response.charset(charset);
}
if(StringUtils.isNotBlank(variable)){
variables.put(variable,response);
}
}
}

View File

@ -0,0 +1,57 @@
package org.spiderflow.core.io;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
public class ScriptResponse {
private int exitValue;
private ByteArrayOutputStream outputStream;
private ByteArrayOutputStream errorStream;
private String defaultCharset;
private Throwable throwable;
public ScriptResponse(int exitValue, ByteArrayOutputStream outputStream, ByteArrayOutputStream errorStream) {
this.exitValue = exitValue;
this.outputStream = outputStream;
this.errorStream = errorStream;
}
public ScriptResponse(int exitValue, ByteArrayOutputStream outputStream, ByteArrayOutputStream errorStream,Throwable throwable) {
this(exitValue,outputStream,errorStream);
this.throwable = throwable;
}
public int getExitValue() {
return exitValue;
}
public ScriptResponse charset(String charset){
this.defaultCharset = charset;
return this;
}
public String getValue() throws UnsupportedEncodingException {
if(errorStream == null){
return null;
}
return defaultCharset == null ? outputStream.toString() : outputStream.toString(defaultCharset);
}
public String getError() throws UnsupportedEncodingException {
if(errorStream == null){
return null;
}
return defaultCharset == null ? errorStream.toString() : errorStream.toString(defaultCharset);
}
public String getStack(){
return this.throwable == null ? null : ExceptionUtils.getStackTrace(this.throwable);
}
}

View File

@ -0,0 +1,49 @@
package org.spiderflow.core.model;
public class ScriptFile {
private String name;
private String path;
private boolean directory;
public ScriptFile(String name, String path, boolean directory) {
this.name = name;
this.path = path;
this.directory = directory;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDirectory() {
return directory;
}
public void setDirectory(boolean directory) {
this.directory = directory;
}
@Override
public String toString() {
return "ScriptFile{" +
"name='" + name + '\'' +
", path='" + path + '\'' +
", directory=" + directory +
'}';
}
}

View File

@ -0,0 +1,38 @@
package org.spiderflow.core.model;
import java.util.ArrayList;
import java.util.List;
public class TreeNode<T> {
private T node;
private List<TreeNode<T>> children;
public TreeNode(T node) {
this.node = node;
}
public T getNode() {
return node;
}
public void setNode(T node) {
this.node = node;
}
public List<TreeNode<T>> getChildren() {
return children;
}
public void setChildren(List<TreeNode<T>> children) {
this.children = children;
}
public void addNode(TreeNode<T> node){
if(this.children == null){
this.children = new ArrayList<>();
}
this.children.add(node);
}
}

View File

@ -0,0 +1,194 @@
package org.spiderflow.core.service;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spiderflow.core.io.ScriptResponse;
import org.spiderflow.core.model.ScriptFile;
import org.spiderflow.core.model.TreeNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Service
public class ScriptService {
@Value("${spider.workspace}")
private String workspace;
@Autowired
private static Logger logger = LoggerFactory.getLogger(ScriptService.class);
/**
* 保存脚本
* @param scriptName 脚本名称
* @param filename 文件名
* @param content 脚本内容
* @throws IOException
*/
public boolean saveScript(String scriptName,String filename,String content){
try {
FileUtils.write(getScriptFile(scriptName,filename),content,"UTF-8");
return true;
} catch (Exception e) {
return false;
}
}
public ScriptResponse execute(String scriptName, String filename, String parameters, int timeout) {
String command = "";
if(filename.endsWith(".py")){
command = "python ";
}else if(filename.endsWith(".js")){
command = "node ";
}else{
return new ScriptResponse(-2,null,null,null);
}
command+= filename;
if(parameters != null){
command += " " + parameters;
}
logger.info("准备执行命令:{}",command);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
try{
File directory = getScriptDirectory(scriptName);
CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
executor.setWatchdog(watchdog);
executor.setWorkingDirectory(directory);
PumpStreamHandler handler = new PumpStreamHandler(outputStream, errorStream);
executor.setStreamHandler(handler);
int value = executor.execute(commandLine);
logger.info("命令{}执行完毕,exitValue={}",command,value);
return new ScriptResponse(value,outputStream,errorStream);
} catch (Exception e) {
logger.error("执行脚本失败",e);
return new ScriptResponse(-1,outputStream,errorStream,e);
}
}
public File getScriptFile(String scriptName,String filename){
File file = new File(getScriptDirectory(scriptName),filename);
File dir = file.getParentFile();
if(!dir.exists()){
dir.mkdirs();
}
return file;
}
private File getScriptDirectory(String scriptName){
File directory = new File(new File(workspace,"scripts"),scriptName);
if(!directory.exists()){
directory.mkdirs();
}
return directory;
}
public String read(String scriptName,String filename) throws IOException {
File file = getScriptFile(scriptName, filename);
if(!file.exists()){
return null;
}
return FileUtils.readFileToString(file,"UTF-8");
}
public boolean rename(String scriptName,String oldName,String newName){
File directory = getScriptDirectory(scriptName);
return new File(directory, oldName).renameTo(new File(directory,newName));
}
/**
* 删除脚本
* @param scriptName 脚本名称
* @param filename 文件名
*/
public boolean removeScript(String scriptName,String filename) {
try {
File scriptDirectory = getScriptDirectory(scriptName);
if(StringUtils.isBlank(filename)){
FileUtils.deleteDirectory(scriptDirectory);
}else{
File file = getScriptFile(scriptName, filename);
if(!file.exists()){
return false;
}
if(file.isDirectory()){
FileUtils.deleteDirectory(file);
return true;
}
return file.delete();
}
} catch (IOException e) {
return false;
}
return true;
}
public List<String> listScript(){
String[] files = new File(workspace, "scripts").list();
if(files != null){
Arrays.sort(files);
return Arrays.asList(files);
}else{
return Collections.emptyList();
}
}
public boolean createScript(String scriptName){
File file = new File(new File(workspace,"scripts"),scriptName);
if(file.exists()){
return false;
}
return file.mkdirs();
}
public boolean createScriptFile(String scriptName,String file,boolean directory){
File dir = getScriptDirectory(scriptName);
File f = new File(dir, file);
File parentFile = f.getParentFile();
parentFile.mkdirs();
try {
return directory ? f.mkdirs() : f.createNewFile();
} catch (IOException e) {
return false;
}
}
/**
* 循环遍历
*/
public TreeNode<ScriptFile> list(String scriptName){
return list(getScriptDirectory(scriptName),null,"");
}
private TreeNode<ScriptFile> list(File file,TreeNode<ScriptFile> node, String parentName){
ScriptFile scriptFile = new ScriptFile(file.getName(),parentName,file.isDirectory());
TreeNode<ScriptFile> current = new TreeNode<>(scriptFile);
if(node != null){
node.addNode(current);
}
if(file.isDirectory()){
File[] files = file.listFiles();
if(files != null){
for (File child : files) {
list(child,current,(parentName.isEmpty() ? "" : parentName + File.separator) + child.getName());
}
}
}
return current;
}
}

View File

@ -0,0 +1,65 @@
package org.spiderflow.controller;
import org.spiderflow.core.io.ScriptResponse;
import org.spiderflow.core.model.ScriptFile;
import org.spiderflow.core.model.TreeNode;
import org.spiderflow.core.service.ScriptService;
import org.spiderflow.model.JsonBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/script")
public class ScriptController {
@Autowired
private ScriptService scriptService;
@RequestMapping("/list")
public JsonBean<List<String>> scripts(){
return new JsonBean<>(scriptService.listScript());
}
@RequestMapping("/files")
public JsonBean<TreeNode<ScriptFile>> files(String script){
return new JsonBean<>(scriptService.list(script));
}
@RequestMapping("/create")
public JsonBean<Boolean> create(String scriptName){
return new JsonBean<>(scriptService.createScript(scriptName));
}
@RequestMapping("/create/file")
public JsonBean<Boolean> createFile(String scriptName,String file,String dir){
return new JsonBean<>(scriptService.createScriptFile(scriptName,file,"1".equalsIgnoreCase(dir)));
}
@RequestMapping("/remove/file")
public JsonBean<Boolean> remove(String scriptName,String file){
return new JsonBean<>(scriptService.removeScript(scriptName,file));
}
@RequestMapping("/rename/file")
public JsonBean<Boolean> rename(String scriptName,String file,String newFile){
return new JsonBean<>(scriptService.rename(scriptName,file,newFile));
}
@RequestMapping("/read")
public JsonBean<String> read(String scriptName,String file) throws IOException {
return new JsonBean<>(scriptService.read(scriptName,file));
}
@RequestMapping("/save")
public JsonBean<Boolean> save(String scriptName,String file,String content){
return new JsonBean<>(scriptService.saveScript(scriptName,file,content));
}
@RequestMapping("/test")
public JsonBean<ScriptResponse> test(String scriptName, String file, String parameter, Integer timeout) {
return new JsonBean<>(scriptService.execute(scriptName,file,parameter,timeout == null ? -1 : timeout));
}
}

View File

@ -38,6 +38,7 @@
<li class="layui-nav-item layui-nav-itemed"><a data-link="variables.html" title="全局变量">全局变量</a></li>
<li class="layui-nav-item layui-nav-itemed"><a data-link="functions.html" title="自定义函数">自定义函数</a></li>
<li class="layui-nav-item layui-nav-itemed"><a data-link="datasources.html" title="数据源管理">数据源管理</a></li>
<li class="layui-nav-item layui-nav-itemed"><a data-link="scripts.html" title="脚本管理">脚本管理</a></li>
</ul>
</div>
</div>

View File

@ -0,0 +1,399 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}
var wordOperators = wordRegexp(["and", "or", "not", "is"]);
var commonKeywords = ["as", "assert", "break", "class", "continue",
"def", "del", "elif", "else", "except", "finally",
"for", "from", "global", "if", "import",
"lambda", "pass", "raise", "return",
"try", "while", "with", "yield", "in"];
var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
"classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
"enumerate", "eval", "filter", "float", "format", "frozenset",
"getattr", "globals", "hasattr", "hash", "help", "hex", "id",
"input", "int", "isinstance", "issubclass", "iter", "len",
"list", "locals", "map", "max", "memoryview", "min", "next",
"object", "oct", "open", "ord", "pow", "property", "range",
"repr", "reversed", "round", "set", "setattr", "slice",
"sorted", "staticmethod", "str", "sum", "super", "tuple",
"type", "vars", "zip", "__import__", "NotImplemented",
"Ellipsis", "__debug__"];
CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins));
function top(state) {
return state.scopes[state.scopes.length - 1];
}
CodeMirror.defineMode("python", function(conf, parserConf) {
var ERRORCLASS = "error";
var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.\\]/;
// (Backwards-compatibility with old, cumbersome config system)
var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters,
parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/]
for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1)
var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
var myKeywords = commonKeywords, myBuiltins = commonBuiltins;
if (parserConf.extra_keywords != undefined)
myKeywords = myKeywords.concat(parserConf.extra_keywords);
if (parserConf.extra_builtins != undefined)
myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
var py3 = !(parserConf.version && Number(parserConf.version) < 3)
if (py3) {
// since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]);
myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]);
var stringPrefixes = new RegExp("^(([rbuf]|(br)|(fr))?('{3}|\"{3}|['\"]))", "i");
} else {
var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;
myKeywords = myKeywords.concat(["exec", "print"]);
myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
"file", "intern", "long", "raw_input", "reduce", "reload",
"unichr", "unicode", "xrange", "False", "True", "None"]);
var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
}
var keywords = wordRegexp(myKeywords);
var builtins = wordRegexp(myBuiltins);
// tokenizers
function tokenBase(stream, state) {
var sol = stream.sol() && state.lastToken != "\\"
if (sol) state.indent = stream.indentation()
// Handle scope changes
if (sol && top(state).type == "py") {
var scopeOffset = top(state).offset;
if (stream.eatSpace()) {
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset)
pushPyScope(state);
else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != "#")
state.errorToken = true;
return null;
} else {
var style = tokenBaseInner(stream, state);
if (scopeOffset > 0 && dedent(stream, state))
style += " " + ERRORCLASS;
return style;
}
}
return tokenBaseInner(stream, state);
}
function tokenBaseInner(stream, state) {
if (stream.eatSpace()) return null;
// Handle Comments
if (stream.match(/^#.*/)) return "comment";
// Handle Number Literals
if (stream.match(/^[0-9\.]/, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
if (stream.match(/^[\d_]+\.\d*/)) { floatLiteral = true; }
if (stream.match(/^\.\d+/)) { floatLiteral = true; }
if (floatLiteral) {
// Float literals may be "imaginary"
stream.eat(/J/i);
return "number";
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true;
// Binary
if (stream.match(/^0b[01_]+/i)) intLiteral = true;
// Octal
if (stream.match(/^0o[0-7_]+/i)) intLiteral = true;
// Decimal
if (stream.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)) {
// Decimal literals may be "imaginary"
stream.eat(/J/i);
// TODO - Can you have imaginary longs?
intLiteral = true;
}
// Zero by itself with no other piece of number.
if (stream.match(/^0(?![\dx])/i)) intLiteral = true;
if (intLiteral) {
// Integer literals may be "long"
stream.eat(/L/i);
return "number";
}
}
// Handle Strings
if (stream.match(stringPrefixes)) {
var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1;
if (!isFmtString) {
state.tokenize = tokenStringFactory(stream.current(), state.tokenize);
return state.tokenize(stream, state);
} else {
state.tokenize = formatStringFactory(stream.current(), state.tokenize);
return state.tokenize(stream, state);
}
}
for (var i = 0; i < operators.length; i++)
if (stream.match(operators[i])) return "operator"
if (stream.match(delimiters)) return "punctuation";
if (state.lastToken == "." && stream.match(identifiers))
return "property";
if (stream.match(keywords) || stream.match(wordOperators))
return "keyword";
if (stream.match(builtins))
return "builtin";
if (stream.match(/^(self|cls)\b/))
return "variable-2";
if (stream.match(identifiers)) {
if (state.lastToken == "def" || state.lastToken == "class")
return "def";
return "variable";
}
// Handle non-detected items
stream.next();
return ERRORCLASS;
}
function formatStringFactory(delimiter, tokenOuter) {
while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
delimiter = delimiter.substr(1);
var singleline = delimiter.length == 1;
var OUTCLASS = "string";
function tokenNestedExpr(depth) {
return function(stream, state) {
var inner = tokenBaseInner(stream, state)
if (inner == "punctuation") {
if (stream.current() == "{") {
state.tokenize = tokenNestedExpr(depth + 1)
} else if (stream.current() == "}") {
if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1)
else state.tokenize = tokenString
}
}
return inner
}
}
function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\{\}\\]/);
if (stream.eat("\\")) {
stream.next();
if (singleline && stream.eol())
return OUTCLASS;
} else if (stream.match(delimiter)) {
state.tokenize = tokenOuter;
return OUTCLASS;
} else if (stream.match('{{')) {
// ignore {{ in f-str
return OUTCLASS;
} else if (stream.match('{', false)) {
// switch to nested mode
state.tokenize = tokenNestedExpr(0)
if (stream.current()) return OUTCLASS;
else return state.tokenize(stream, state)
} else if (stream.match('}}')) {
return OUTCLASS;
} else if (stream.match('}')) {
// single } in f-string is an error
return ERRORCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors)
return ERRORCLASS;
else
state.tokenize = tokenOuter;
}
return OUTCLASS;
}
tokenString.isString = true;
return tokenString;
}
function tokenStringFactory(delimiter, tokenOuter) {
while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
delimiter = delimiter.substr(1);
var singleline = delimiter.length == 1;
var OUTCLASS = "string";
function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\\]/);
if (stream.eat("\\")) {
stream.next();
if (singleline && stream.eol())
return OUTCLASS;
} else if (stream.match(delimiter)) {
state.tokenize = tokenOuter;
return OUTCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors)
return ERRORCLASS;
else
state.tokenize = tokenOuter;
}
return OUTCLASS;
}
tokenString.isString = true;
return tokenString;
}
function pushPyScope(state) {
while (top(state).type != "py") state.scopes.pop()
state.scopes.push({offset: top(state).offset + conf.indentUnit,
type: "py",
align: null})
}
function pushBracketScope(stream, state, type) {
var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1
state.scopes.push({offset: state.indent + hangingIndent,
type: type,
align: align})
}
function dedent(stream, state) {
var indented = stream.indentation();
while (state.scopes.length > 1 && top(state).offset > indented) {
if (top(state).type != "py") return true;
state.scopes.pop();
}
return top(state).offset != indented;
}
function tokenLexer(stream, state) {
if (stream.sol()) state.beginningOfLine = true;
var style = state.tokenize(stream, state);
var current = stream.current();
// Handle decorators
if (state.beginningOfLine && current == "@")
return stream.match(identifiers, false) ? "meta" : py3 ? "operator" : ERRORCLASS;
if (/\S/.test(current)) state.beginningOfLine = false;
if ((style == "variable" || style == "builtin")
&& state.lastToken == "meta")
style = "meta";
// Handle scope changes.
if (current == "pass" || current == "return")
state.dedent += 1;
if (current == "lambda") state.lambda = true;
if (current == ":" && !state.lambda && top(state).type == "py")
pushPyScope(state);
if (current.length == 1 && !/string|comment/.test(style)) {
var delimiter_index = "[({".indexOf(current);
if (delimiter_index != -1)
pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
delimiter_index = "])}".indexOf(current);
if (delimiter_index != -1) {
if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent
else return ERRORCLASS;
}
}
if (state.dedent > 0 && stream.eol() && top(state).type == "py") {
if (state.scopes.length > 1) state.scopes.pop();
state.dedent -= 1;
}
return style;
}
var external = {
startState: function(basecolumn) {
return {
tokenize: tokenBase,
scopes: [{offset: basecolumn || 0, type: "py", align: null}],
indent: basecolumn || 0,
lastToken: null,
lambda: false,
dedent: 0
};
},
token: function(stream, state) {
var addErr = state.errorToken;
if (addErr) state.errorToken = false;
var style = tokenLexer(stream, state);
if (style && style != "comment")
state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style;
if (style == "punctuation") style = null;
if (stream.eol() && state.lambda)
state.lambda = false;
return addErr ? style + " " + ERRORCLASS : style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase)
return state.tokenize.isString ? CodeMirror.Pass : 0;
var scope = top(state), closing = scope.type == textAfter.charAt(0)
if (scope.align != null)
return scope.align - (closing ? 1 : 0)
else
return scope.offset - (closing ? hangingIndent : 0)
},
electricInput: /^\s*[\}\]\)]$/,
closeBrackets: {triples: "'\""},
lineComment: "#",
fold: "indent"
};
return external;
});
CodeMirror.defineMIME("text/x-python", "python");
var words = function(str) { return str.split(" "); };
CodeMirror.defineMIME("text/x-cython", {
name: "python",
extra_keywords: words("by cdef cimport cpdef ctypedef enum except "+
"extern gil include nogil property public "+
"readonly struct union DEF IF ELIF ELSE")
});
});

View File

@ -587,6 +587,11 @@ $(function(){
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD1ElEQVRYR7WXUXIaRxCGv4YqR2+WH2zxZukEUU5gdAKjExhfILAnMD7BohMYn8D4BEYnsHQCobfd5CHkDVIFnerZmc0sLAJjMlVUSbszPX93//13r7DnOk319JcGb0TpAOfRzyxMw0+F8WLF7SyR2T6mZdemVqrnCB+A7sZe5dE9E17X2BmhfMwSMXBb11YAzmPhg0DfnVYeFcYKkz8SGddZfJVqR6At0ClBCYP5ipttEakF4C//JnCpyt/AIE9kuCta8fuzVA34QITnCncL5aoOxAaAl6leNgS7/FSVrwvo7pvPdYDOERiJ8FZhtlKu/kzkLt5XAeA9f/CX3+SJFOH/yXWW6lCEnoFYKBexQyWAtbAf7fKAPQJRSUcJoNygfM0TsVI7+jpLdWzp8NUxKAoI8KX2YIRbwPmhOd+F2HNiasREubASDQBGCO9USdbZ3kq1TRPNfpfb9Qsc8CavWfIY17sRudnk+XzJ/bozrVQHTleUz1kiXTFUJ8JfVudZIqZwbjkjwhevePZoulSuA4tLQ36/wjDvS+IjOkF4g3KVJTKpAT41nZgrL8TEoyF8UaVCvFaqbpOVooEQ4Ver57wvv7moCN8sZVKIU8eH9X2WyKiV6i4ALuIr5VpaqZb/BIWLOHGfJ3LpvXKAsr7YGRdGM2Bn1p3YBSDstzSYMYfWDIdQBQ9RbrNE2h6A9YLzLJFBlEcX4vX9uwA4e0NVs28ASs/iXLkN1uWKPFYayrEAWErFI6kQ0IAEXbC/rQmJchMIdRQA3nEHIJCrhq0mFt2ysxXt9TgpKAFsSUElHakaiE+m5XlfXhwlAua4cl9LwjpFi8l6LACBhBtl6EUo9Wo1isUlLkMj6BJmTaHnUuWrZlcVVMqwTog8gO+BG14tv7syjHTApNuGFhMVF7X/OLK/EJVSDNOsLxdR+7wz9aukw+u3CZUrIWsq0QqTzwmMnRRXX8aaUpb+1mbkgMHQZNa3zfEc+qG5mPiY/juJLuR6YvPjEjoN6LrIrIGzAWejGfn82uT7UDex1BHy0GfxxFVpxx6E03cTnbwv14de8tS5iJxOT3xkiyN+WJj4kP5/I5lyv4B2SOXmUApuYon7+89GIxr3Niau+rHcCFWAGC+U94eOaD7nn+xDxWaHFbSfHMuDp5V0wAxhkPfk5kciYWxXoedH/ErYYztPfpqd2GdZ8V1oa2oRQZjkPbGy21he1N4CNkMU453ycQ7DH/o0iy276QisQgq1q64wJ5SzZPla+WznDv44Xb/J0vIM2o1CmEwJT4NSuq5mqYLpCsb/wGRf3vwLgODoY+vqQ1gAAAAASUVORK5CYII=',
title : '执行SQL',
desc : '执行sql需配置数据源sql执行结果存于变量rs中<br/>语句类型为select返回:List&lt;Map&lt;String,Object&gt;&gt;<br/>语句类型为selectOne返回:Map&lt;String,Object&gt;<br/>语句类型为selectInt返回:Integer<br/>语句类型为insertupdatedelete返回:int批量操作返回int数组<br/>sql中变量必须用 # # 包裹#${title}#'
},{
name : 'script',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADD0lEQVRYR92XTUhUURTH//8xyzYRJM2MuZCoFrk0iD4WFQiJRSsnF4GmIFHkvEdmUIEKRZjVm5EoDErcmS4rSgpzIX1ArcIWYSZt5vkBSUWM0zgn3nPe9NSx+XBeQRcGZuaej98999xzzyX+8aDl3xuUMonhcE54iBFdYX86thIAHk1aQbSko5SmzGld4c1UsksBBG2plFLOxxdC4krIz/N/kk8GsF9XOZTSyTICHk32gXiemCZ6NnjRMOJjJJmK8wDzXgchqNdVji+G+FsAgGA05kLNpJ8v7BA5BzCMewIiy+zQLIEjIYUD1rwzAEFpgaAKQOkSEEGbrrLVUYBkq08k5/8PUCV56Ofc4iisOAKeoLRTMBZS2GU37g3InZigd0LlYFFAymOCE9EIaqfP8ZtdLmuALZ2y5rvgFgR1AEbggk9v5HvDuDcoB0Xw2HTkQili6Isn3sBcBLVTzdQtiKwAim/Lpp+z6CZQDmA4KvBNqwzZjD4EUSnE5Qk/Lxa2S1HeavSR2APgVSyCY5PN/GgeTatCZpKEnoAYRWMXiAcF6+AbP86w5dytSR2JuyA+hGPYOaNyxpgrviFroy4zEocAvNQV7l4JQC+AoySe5efD9/kkvxjGSjRZH3bhNQTbRFA/ofKeBVbSLQXhr+iDmFf7fV1hddYA8ap2FcBZAGMkfCE/37qDcomCCxA80lUaKzVHoSbeVTRXvxdAh66weUU5kFAOyBkA18zfRKXh2PxKVIT8fGKCdsp2WxI26Qqv5+QUJCCCUgPBD6PTcWtywEVUhxQ2LHJSARc26n725LwOJCuvmfyX1SnIxEE6smbbBwzZGx5HbsN0YCwZRwE8AXljONIV7lgOyjEAtyabSZhVUFeY8LMYxBEAY69dwNMYMRx3+LvJFbzTVTY6tgUeTUpAfAJgNKAlSUNvuw+ciUBQOiBoWjYZUwBk/DCJAl32WzJewo0ELMs8ApmcIUtWsOQx4w1KgwgWNC+W+Fw+tk6d4uh8ZY+PlTxOk0XAjILRAwDGJzHsHfECgGwWngudX+XU9zD0knfOAAAAAElFTkSuQmCC',
title : '执行脚本',
desc : '单独执行脚本方法'
},{
name : 'function',
image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAgCAYAAACPb1E+AAAC9UlEQVRYR+2YP0zUUBjAf9+RKJPgQK6THptxERPiZOTUxEUj/tlMjCS62xoT3YARl5bJTXByBGcHISQmxhjQ6A6TPRg8JrnE62de6Z0Htne9XhMw4W1N3/u+X7//rwJgzeokygRQMs+HYSksBsr0liNrYrk6gTB3GMBiGNZ9W4al6OqiCOOqfFGYKkD1oIEVRkRwQw5l2FhyCWEMZdp3ZOqgARv6LU/1CDIPbxxZMg8rhmUxj5gcdHWw6khiJSh6+lVgAGXMd2S90/79H9czpOXpd+CsKk7FEe8fBab2whwCdeF6n/ISOFUPuLT1RFbSWLsnSMvTOVUmREJVM74tz1uVGov1C6tR9/rs2zJqefoL6FfYDpSy6SKdQDNDGkAI2yeqvKk4ci/GilMIk40aZ1w95OpIQVgWOKFQDZTLnUAzQbYCorz2HQlhW5flakmFVYFBVWYrjtiN9yEoLIkwkAa0a0jL0x8m4SLrxAKGGenqPMIDVbZrUNqfWE1Qk1AFqAc83HLkVZzrs0AGgKhQDwJG41xluVpGeB99SGKLtTx9i3LTJFUAC5u23MkFcsjTZwWYFjie5CrLUwNYRtnwHYkd+fbENHyr/eZG9als5AJphLSLqdZxL1BubzqyGFP3mkmXFNN74jtrMW8FRdgB7u4EfGiWHGXZd6QcA7iCcBEz1yQkXa7F3ID2wUeEY0HATJ9wRoVxo6SunI+N18gqCu8qtlzrVCNzaYtFVx8BVwJ4UYB5Ec4pfKrYciEOoOiqjXC1ptxv10pzcXccQBQC5VqdhaQkSGO5XN2dRWGWM13XySxKej1zBNmrBRvnYy25fyDIS1kWOdG49zNqseG92xPhcSRsqWuhgoXSn3jOFH3F71Kuaatha91RTpp7d0lhzYxQXQra3W66yO4AHL86vW+nNOpOofgI9JaZAzOAmt80p9tAmgFivlu5Aaw1ZoB2Nkglt/kHJNmSsf08lfBoU2pIy9W/14JuNCTs9e3olpRC1n8B+Qd0Dhp9ddQMugAAAABJRU5ErkJggg==',

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,97 @@
/*-------------------------------------
zTree Style
version: 3.5.19
author: Hunter.z
email: hunter.z@263.net
website: http://code.google.com/p/jquerytree/
-------------------------------------*/
.ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif}
.ztree {margin:0; padding:5px; color:#333}
.ztree li{padding:0; margin:0; list-style:none; line-height:14px; text-align:left; white-space:nowrap; outline:0}
.ztree li ul{ margin:0; padding:0 0 0 18px}
.ztree li ul.line{ background:url(./img/line_conn.gif) 0 0 repeat-y;}
.ztree li a {padding:1px 3px 0 0; margin:0; cursor:pointer; height:17px; color:#333; background-color: transparent;
text-decoration:none; vertical-align:top; display: inline-block}
.ztree li a:hover {text-decoration:underline}
.ztree li a.curSelectedNode {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}
.ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}
.ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#316AC5; color:white; height:16px; border:1px #316AC5 solid;
opacity:0.8; filter:alpha(opacity=80)}
.ztree li a.tmpTargetNode_prev {}
.ztree li a.tmpTargetNode_next {}
.ztree li a input.rename {height:14px; width:80px; padding:0; margin:0;
font-size:12px; border:1px #7EC4CC solid; *border:0px}
.ztree li span {line-height:16px; margin-right:2px}
.ztree li span.button {line-height:0; margin:0; width:16px; height:16px; display: inline-block; vertical-align:middle;
border:0 none; cursor: pointer;outline:none;
background-color:transparent; background-repeat:no-repeat; background-attachment: scroll;
background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")}
.ztree li span.button.chk {width:13px; height:13px; margin:0 3px 0 0; cursor: auto}
.ztree li span.button.chk.checkbox_false_full {background-position:0 0}
.ztree li span.button.chk.checkbox_false_full_focus {background-position:0 -14px}
.ztree li span.button.chk.checkbox_false_part {background-position:0 -28px}
.ztree li span.button.chk.checkbox_false_part_focus {background-position:0 -42px}
.ztree li span.button.chk.checkbox_false_disable {background-position:0 -56px}
.ztree li span.button.chk.checkbox_true_full {background-position:-14px 0}
.ztree li span.button.chk.checkbox_true_full_focus {background-position:-14px -14px}
.ztree li span.button.chk.checkbox_true_part {background-position:-14px -28px}
.ztree li span.button.chk.checkbox_true_part_focus {background-position:-14px -42px}
.ztree li span.button.chk.checkbox_true_disable {background-position:-14px -56px}
.ztree li span.button.chk.radio_false_full {background-position:-28px 0}
.ztree li span.button.chk.radio_false_full_focus {background-position:-28px -14px}
.ztree li span.button.chk.radio_false_part {background-position:-28px -28px}
.ztree li span.button.chk.radio_false_part_focus {background-position:-28px -42px}
.ztree li span.button.chk.radio_false_disable {background-position:-28px -56px}
.ztree li span.button.chk.radio_true_full {background-position:-42px 0}
.ztree li span.button.chk.radio_true_full_focus {background-position:-42px -14px}
.ztree li span.button.chk.radio_true_part {background-position:-42px -28px}
.ztree li span.button.chk.radio_true_part_focus {background-position:-42px -42px}
.ztree li span.button.chk.radio_true_disable {background-position:-42px -56px}
.ztree li span.button.switch {width:18px; height:18px}
.ztree li span.button.root_open{background-position:-92px -54px}
.ztree li span.button.root_close{background-position:-74px -54px}
.ztree li span.button.roots_open{background-position:-92px 0}
.ztree li span.button.roots_close{background-position:-74px 0}
.ztree li span.button.center_open{background-position:-92px -18px}
.ztree li span.button.center_close{background-position:-74px -18px}
.ztree li span.button.bottom_open{background-position:-92px -36px}
.ztree li span.button.bottom_close{background-position:-74px -36px}
.ztree li span.button.noline_open{background-position:-92px -72px}
.ztree li span.button.noline_close{background-position:-74px -72px}
.ztree li span.button.root_docu{ background:none;}
.ztree li span.button.roots_docu{background-position:-56px 0}
.ztree li span.button.center_docu{background-position:-56px -18px}
.ztree li span.button.bottom_docu{background-position:-56px -36px}
.ztree li span.button.noline_docu{ background:none;}
.ztree li span.button.ico_open{margin-right:2px; background-position:-110px -16px; vertical-align:top; *vertical-align:middle}
.ztree li span.button.ico_close{margin-right:2px; background-position:-110px 0; vertical-align:top; *vertical-align:middle}
.ztree li span.button.ico_docu{margin-right:2px; background-position:-110px -32px; vertical-align:top; *vertical-align:middle}
.ztree li span.button.edit {margin-right:2px; background-position:-110px -48px; vertical-align:top; *vertical-align:middle}
.ztree li span.button.remove {margin-right:2px; background-position:-110px -64px; vertical-align:top; *vertical-align:middle}
.ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle}
ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)}
span.tmpzTreeMove_arrow {width:16px; height:16px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute;
background-color:transparent; background-repeat:no-repeat; background-attachment: scroll;
background-position:-110px -80px; background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")}
ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)}
.zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute}
/* level style*/
/*.ztree li span.button.level0 {
display:none;
}
.ztree li ul.level0 {
padding:0;
background:none;
}*/

View File

@ -0,0 +1,67 @@
<div class="layui-tab layui-tab-fixed layui-tab-brief">
<ul class="layui-tab-title">
<li class="layui-this">配置</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<form class="layui-form editor-form-node">
<div class="layui-row">
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">节点名称</label>
<div class="layui-input-block">
<input type="text" name="value" placeholder="请输入节点名称" value="{{=d.value}}" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">结果变量</label>
<div class="layui-input-block">
<input type="text" name="scriptVariable" placeholder="请输入结果变量" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.scriptVariable}}">
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">超时时间</label>
<div class="layui-input-block">
<input type="text" name="scriptTimeout" placeholder="请输入超时时间" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.scriptTimeout}}">
</div>
</div>
</div>
</div>
<div class="layui-row">
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">脚本名</label>
<div class="layui-input-block">
<input type="text" name="scriptName" placeholder="请输入脚本名" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.scriptName}}">
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">脚本文件</label>
<div class="layui-input-block">
<input type="text" name="scriptFile" placeholder="请输入脚本文件" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.scriptFile}}">
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-form-item">
<label class="layui-form-label">结果编码</label>
<div class="layui-input-block">
<input type="text" name="scriptCharset" placeholder="请输入结果编码" autocomplete="off" class="layui-input input-default" value="{{=d.data.object.scriptCharset}}">
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">执行参数</label>
<div class="layui-input-block" codemirror="scriptParameter" placeholder="请输入执行参数" data-value="{{=d.data.object.scriptParameter}}"></div>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,438 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DataSource</title>
<link rel="stylesheet" href="js/layui/css/layui.css" />
<link rel="stylesheet" href="css/layui-blue.css" />
<link rel="stylesheet" href="js/ztree/zTreeStyle/zTreeStyle.css">
<link rel="stylesheet" href="js/codemirror/codemirror.css">
<link rel="stylesheet" href="js/codemirror/dracula.css">
<script type="text/javascript" src="js/layui/layui.all.js" ></script>
<script type="text/javascript" src="js/codemirror/codemirror.js" ></script>
<script type="text/javascript" src="js/codemirror/python.js" ></script>
<script type="text/javascript" src="js/codemirror/javascript.js" ></script>
<script>var $ = layui.$;var jQuery = $;</script>
<script type="text/javascript" src="js/ztree/jquery.ztree.all.min.js" ></script>
<style type="text/css">
html,body,script-container{
width : 100%;
height: 100%;
box-sizing: border-box;
position: relative;
}
.script-container .script-left-container{
width : 200px;
height : 100px;
position: fixed;
}
.script-container .script-right-container{
right : 0;
left : 205px;
height : 100%;
position: fixed;
}
.script-tree-files{
top : 50px;
bottom:0px;
position: fixed;
width: 200px;
border : 1px solid #eee;
}
.script-right-container .script-buttons{
display: inline-block;
padding-left: 20px;
}
.script-right-container .script-buttons input{
display: inline-block;
}
.script-content{
top : 50px;
bottom: 0px;
position: fixed;
left : 225px;
right : 15px;
border:1px solid #eee;
}
#codemirror-content{
width:100%;
height:100%;
}
.ztree-contextmenu{
position: absolute;
z-index: 999;
border:1px solid #aaa;
background:#f2f2f2;
font-size : 12px;
}
.ztree-contextmenu li{
border-bottom: 1px solid #cdcdcd;
padding : 0px 10px;
height : 24px;
line-height: 24px;
cursor: pointer;
}
.ztree-contextmenu li:hover{
background:#1a7dc4;
color : #fff;
}
.ztree-contextmenu li:last-child{
border-bottom: none;
}
</style>
</head>
<body style="padding:5px;">
<div class="script-container layui-form">
<div class="script-left-container">
<div class="script-select-container">
<select id="script-select" lay-filter="script-select"><option>请选择脚本</option></select>
</div>
<div class="script-tree-files">
<ul id="tree" class="ztree"></ul>
</div>
</div>
<div class="script-right-container">
<div class="script-buttons">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal btn-create-script">创建脚本</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal btn-refresh">刷新</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal btn-save">保存当前文件</button>
<input type="text" class="layui-input" id="path" value="./" readonly style="width: 200px"/>
<input type="text" class="layui-input" id="parameter" value="" placeholder="运行参数" style="width: 400px"/>
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal btn-test">测试执行</button>
</div>
<div class="script-content">
<textarea id="codemirror-content"></textarea>
</div>
</div>
</div>
<script>
String.prototype.endWith = function (str) {
if (str == null || str == "" || this.length == 0 || str.length > this.length) {
return false;
}
if (this.substring(this.length - str.length) == str) {
return true;
} else {
return false;
}
return true;
}
$.ajax({
url : 'script/list',
success : function(data){
var scripts = data.data;
for(var i=0,len = scripts.length;i<len;i++){
$('#script-select').append('<option value="'+scripts[i]+'">'+scripts[i]+'</option>');
}
layui.form.render();
}
})
var currentScript;
var currentFile;
layui.form.on('select(script-select)',function(data){
renderScriptTree(data.value);
})
function renderScriptTree(scriptName,passCodemirror){
currentScript = scriptName;
$.ajax({
url : 'script/files',
type : 'post',
data : {
script : scriptName
},
success : function(data){
var process = function(item){
item.name = item.node.name;
item.isParent = item.node.directory;
if(item.children){
for(var i=0,len = item.children.length;i<len;i++){
process(item.children[i]);
}
}
return item;
}
var tree = data.data.children || [];
for (var i = 0; i < tree.length; i++) {
process(tree[i])
}
var ztree = $.fn.zTree.init($("#tree"), {
callback : {
onClick : function(event,treeId,treeNode){
if(!treeNode.isParent){
loadScript(treeNode.node.path);
}
},
onRightClick : function(event,treeId,treeNode){
var path = treeNode&&treeNode.node&&treeNode.node.path || '';
var name = treeNode&&treeNode.node&&treeNode.node.name || '';
var html = '<ul class="ztree-contextmenu" data-name="'+name+'" data-path="'+path+'">';
if(treeNode.isRoot){
html+='<li class="ztre-create-file">添加文件</li>';
html+='<li class="ztre-create-directory">添加文件夹</li>';
}else if(treeNode.isParent){
html+='<li class="ztre-create-file">添加文件</li>';
html+='<li class="ztre-create-directory">添加文件夹</li>';
html+='<li class="ztree-remove-file">删除文件夹</li>';
html+='<li class="ztree-rename-file">重命名</li>';
}else{
html+='<li class="ztree-remove-file">删除文件</li>';
html+='<li class="ztree-rename-file">重命名</li>';
}
html+='</ul>';
var y = event.clientY + document.body.scrollTop;
var x = event.clientX + document.body.scrollLeft;
$('.ztree-contextmenu').remove();
$('body').append(html);
$('.ztree-contextmenu').css({"top":y+"px", "left":x+"px"});
}
}
}, [{
name : scriptName,
isRoot :true,
children : tree
}]);
ztree.expandAll(true);
if(passCodemirror === undefined){
createCodeMirror('text');
}
}
})
}
function getMode(filename){
if(filename.endWith('.py')){
return 'python';
}else if(filename.endWith('.js')){
return 'javascript';
}
return 'text';
}
function loadScript(filename){
$.ajax({
url : 'script/read',
type : 'post',
data : {
file : filename,
scriptName : currentScript
},
success : function(data){
var content = data.data || '';
currentFile = filename;
$('#path').val(filename);
createCodeMirror(getMode(filename),content);
}
})
}
function saveFile(filename,content){
$.ajax({
url : 'script/save',
type : 'post',
data : {
file : filename,
content : content,
scriptName : currentScript
},
success : function(data){
if(data.data){
layui.layer.msg('保存成功');
}else{
layui.layer.msg('保存失败');
}
}
})
}
function removeFile(filename){
$.ajax({
url : 'script/remove/file',
type : 'post',
data : {
file : filename,
scriptName : currentScript
},
success : function(data){
if(data.data){
layui.layer.msg('删除成功');
renderScriptTree(currentScript);
}else{
layui.layer.msg('删除失败');
}
}
})
}
function renameFile(oldName,newName){
$.ajax({
url : 'script/rename/file',
type : 'post',
data : {
file : oldName,
newFile : newName,
scriptName : currentScript
},
success : function(data){
if(data.data){
layui.layer.msg('重命名成功');
renderScriptTree(currentScript);
}else{
layui.layer.msg('重命名失败');
}
}
})
}
function testRun(){
var parameter = $('#parameter').val();
var path = $('#path').val();
$.ajax({
url : 'script/test',
type : 'post',
data : {
file : path,
parameter : parameter,
scriptName : currentScript
},
success : function(data){
data = data.data;
if(data.exitValue == 0){
layui.layer.alert(data.value,{
title : '执行成功'
});
}else if(data.exitValue == -2){
layui.layer.msg('不支持的脚本类型');
}else {
var message = ((data.value || '') + '\r\n' + (data.error || '')) + '\r\n' + (data.stack || '');
message = message.replace(/\n/g,'<br>').replace(/ /g,'&nbsp;').replace(/\t/g,'&nbsp;&nbsp;&nbsp;&nbsp;');
layui.layer.alert('<div style="font-weight: bold;font-family:Consolas;font-size:12px;">' + message + '</div>',{
title : '执行失败,exitValue=' + data.exitValue,
area:['800px','400px']
});
}
}
})
}
function createFile(filename,path,dir){
$.ajax({
url : 'script/create/file',
type : 'post',
data : {
file : (path||'') + '/'+ filename,
dir : dir,
scriptName : currentScript
},
success : function(data){
if(data.data){
layui.layer.msg('创建文件'+(dir == '1' ? '夹' : '') +'成功');
if(dir == '0'){
renderScriptTree(currentScript,true);
createCodeMirror(getMode(filename));
loadScript((path ? path + '/' : '') + filename);
}else{
renderScriptTree(currentScript);
}
}else{
layui.layer.msg('创建失败');
}
}
})
}
var $instance;
function createCodeMirror(mode,content){
if($instance){
$instance.toTextArea();
}
var element = document.getElementById('codemirror-content');
$instance = CodeMirror.fromTextArea(element,{
mode: mode,
lineNumbers : true,
theme: 'dracula',
})
$instance.setValue(content || '');
}
$('body').on('click','.btn-create-script',function(){
layui.layer.prompt({
title : '请输入脚本名称'
},function(value,index){
layui.layer.close(index);
$.ajax({
url : 'script/create',
type : 'post',
data : {
scriptName : value
},
success : function(data){
if(data.data){
layui.layer.msg('创建成功');
$('#script-select').append('<option value="'+value+'" selected>'+value+'</option>');
layui.form.render();
$('#path').val('');
currentFile = '';
renderScriptTree(value);
}else{
layui.layer.msg('创建失败');
}
}
})
})
}).on('click','.ztree-contextmenu .ztre-create-directory',function(){
var path = $(this).parent().data('path');
layui.layer.prompt({
title : '请输入文件夹名称'
},function(value,index){
createFile(value,path,'1');
layui.layer.close(index);
})
}).on('click','.ztree-contextmenu .ztre-create-file',function(){
var path = $(this).parent().data('path');
layui.layer.prompt({
title : '请输入文件名称'
},function(value,index){
createFile(value,path,'0');
layui.layer.close(index);
})
}).on('click','.ztree-contextmenu .ztree-remove-file',function(){
var path = $(this).parent().data('path');
layui.layer.confirm('是否删除该文件',{
title : '是否删除'
},function(index){
removeFile(path);
layui.layer.close(index);
})
}).on('click','.ztree-contextmenu .ztree-rename-file',function(){
var path = $(this).parent().data('path');
var oldName = $(this).parent().data('name');
layui.layer.prompt({
title : '重命名',
value : oldName
},function(newName,index){
renameFile(path,path.substring(0,path.length - oldName.length) + newName);
layui.layer.close(index);
})
}).on('click','.btn-save',function(){
if($instance && currentFile){
var content = $instance.getValue();
saveFile(currentFile,content);
}else{
layui.layer.msg('请选择文件');
}
}).on('click','.btn-test',function(){
if($instance && currentFile){
testRun(currentFile);
}else{
layui.layer.msg('请选择文件');
}
}).on('click','.btn-refresh',function(){
if($instance && currentFile){
loadScript(currentFile);
}
}).on('click',function(){
$('.ztree-contextmenu').remove();
})
createCodeMirror('text');
</script>
<script type="text/html" id="buttons">
<a class="layui-btn layui-btn-sm btn-edit" data-id="{{d.id}}">编辑</a>
<a class="layui-btn layui-btn-sm btn-remove" data-id="{{d.id}}">删除</a>
</script>
</body>
</html>