mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-12-02 11:58:01 +08:00
fix SSH 命令脚本、服务端脚本、插件端脚本执行参数优化
This commit is contained in:
parent
51ed92c63d
commit
f910fb0a45
10
CHANGELOG.md
10
CHANGELOG.md
@ -11,6 +11,16 @@
|
||||
3. 【server】修复 节点分发 webhook 输入框的错别字(感谢 @大灰灰 )
|
||||
4. 【server】修复 工作空间环境变量操作日志记录错误问题
|
||||
5. 【all】更新 fastjson2 版本
|
||||
6. 【all】优化 SSH 命令脚本、服务端脚本、插件端脚本执行参数优化
|
||||
(感谢 [@大灰灰大](https://gitee.com/linjianhui) [Gitee issues I6IPDY](https://gitee.com/dromara/Jpom/issues/I6IPDY) )
|
||||
|
||||
### ❌ 不兼容功能
|
||||
|
||||
1. 【server】删除 COMMAND_INFO 表 type 字段
|
||||
|
||||
### ⚠️ 注意
|
||||
|
||||
SSH 命令脚本、服务端脚本、插件端脚本默认参数规则变化:参数描述将必填,默认参数在手动执行时无法删除并且可以查看对应参数描述
|
||||
|
||||
------
|
||||
|
||||
|
@ -25,6 +25,7 @@ package io.jpom.model.data;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.jpom.JpomApplication;
|
||||
import io.jpom.script.CommandParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@ -69,6 +70,10 @@ public class NodeScriptModel extends BaseWorkspaceModel {
|
||||
return StrUtil.emptyToDefault(lastRunUser, StrUtil.DASHED);
|
||||
}
|
||||
|
||||
public void setDefArgs(String defArgs) {
|
||||
this.defArgs = CommandParam.convertToParam(defArgs);
|
||||
}
|
||||
|
||||
public File scriptPath() {
|
||||
return scriptPath(getId());
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public class NodeScriptProcessBuilder extends BaseRunScript implements Runnable
|
||||
//
|
||||
String script = FileUtil.getAbsolutePath(scriptFile);
|
||||
processBuilder = new ProcessBuilder();
|
||||
List<String> command = StrUtil.splitTrim(args, StrUtil.SPACE);
|
||||
List<String> command = CommandParam.toCommandList(args);
|
||||
command.add(0, script);
|
||||
CommandUtil.paddingPrefix(command);
|
||||
log.debug(CollUtil.join(command, StrUtil.SPACE));
|
||||
|
125
modules/common/src/main/java/io/jpom/script/CommandParam.java
Normal file
125
modules/common/src/main/java/io/jpom/script/CommandParam.java
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Code Technology Studio
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package io.jpom.script;
|
||||
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.JSONValidator;
|
||||
import io.jpom.model.BaseJsonModel;
|
||||
import io.jpom.util.StringUtil;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 脚本参数
|
||||
*
|
||||
* @author bwcx_jzy
|
||||
* @since 2023/3/13
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CommandParam extends BaseJsonModel {
|
||||
/**
|
||||
* 参数值
|
||||
*/
|
||||
private String value;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String desc;
|
||||
|
||||
public static String convertToParam(String defArgs) {
|
||||
JSONValidator.Type type = StringUtil.validatorJson(defArgs);
|
||||
if (type == null || type == JSONValidator.Type.Value) {
|
||||
// 旧版本的数据
|
||||
List<CommandParam> commandParams = CommandParam.convertLineStr(defArgs);
|
||||
return commandParams == null ? null : JSONObject.toJSONString(commandParams);
|
||||
} else if (type == JSONValidator.Type.Object) {
|
||||
return defArgs;
|
||||
} else {
|
||||
return defArgs;
|
||||
}
|
||||
}
|
||||
|
||||
public static String toCommandLine(String params) {
|
||||
JSONValidator.Type type = StringUtil.validatorJson(params);
|
||||
if (type == null || type == JSONValidator.Type.Value) {
|
||||
// 兼容旧数据
|
||||
return params;
|
||||
}
|
||||
List<CommandParam> paramList = params(params);
|
||||
return Optional.ofNullable(paramList)
|
||||
.map(commandParams -> commandParams.stream()
|
||||
.map(CommandParam::getValue)
|
||||
.collect(Collectors.joining(StrUtil.SPACE)))
|
||||
.orElse(StrUtil.EMPTY);
|
||||
}
|
||||
|
||||
public static List<String> toCommandList(String params) {
|
||||
JSONValidator.Type type = StringUtil.validatorJson(params);
|
||||
if (type == null || type == JSONValidator.Type.Value) {
|
||||
// 兼容旧数据
|
||||
return StrUtil.splitTrim(params, StrUtil.SPACE);
|
||||
}
|
||||
List<CommandParam> paramList = params(params);
|
||||
return Optional.ofNullable(paramList)
|
||||
.map(commandParams -> commandParams.stream()
|
||||
.map(CommandParam::getValue)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()))
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
public static List<CommandParam> params(String defParams) {
|
||||
return StringUtil.jsonConvertArray(defParams, CommandParam.class);
|
||||
}
|
||||
|
||||
public static String checkStr(String str) {
|
||||
return Opt.ofBlankAble(str)
|
||||
.map(s -> {
|
||||
List<CommandParam> params = params(s);
|
||||
return JSONObject.toJSONString(params);
|
||||
}).orElse(StrUtil.EMPTY);
|
||||
}
|
||||
|
||||
public static List<CommandParam> convertLineStr(String defArgs) {
|
||||
List<String> list = StrUtil.splitTrim(defArgs, StrUtil.SPACE);
|
||||
return Optional.ofNullable(list)
|
||||
.map(strings -> {
|
||||
List<CommandParam> commandParams1 = new ArrayList<>(strings.size());
|
||||
for (int i = 0; i < strings.size(); i++) {
|
||||
CommandParam commandParam = new CommandParam();
|
||||
commandParam.setValue(strings.get(i));
|
||||
commandParam.setDesc("参数" + (i + 1));
|
||||
commandParams1.add(commandParam);
|
||||
}
|
||||
return commandParams1;
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,6 @@ import io.jpom.common.*;
|
||||
import io.jpom.common.forward.NodeForward;
|
||||
import io.jpom.common.forward.NodeUrl;
|
||||
import io.jpom.common.validator.ValidatorItem;
|
||||
import top.jpom.model.PageResultDto;
|
||||
import io.jpom.model.data.NodeModel;
|
||||
import io.jpom.model.script.ScriptModel;
|
||||
import io.jpom.model.user.UserModel;
|
||||
@ -38,6 +37,7 @@ import io.jpom.permission.ClassFeature;
|
||||
import io.jpom.permission.Feature;
|
||||
import io.jpom.permission.MethodFeature;
|
||||
import io.jpom.permission.SystemPermission;
|
||||
import io.jpom.script.CommandParam;
|
||||
import io.jpom.service.node.script.NodeScriptServer;
|
||||
import io.jpom.service.script.ScriptExecuteLogServer;
|
||||
import io.jpom.service.script.ScriptServer;
|
||||
@ -48,6 +48,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.jpom.model.PageResultDto;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
@ -118,7 +119,7 @@ public class ScriptController extends BaseServerController {
|
||||
scriptModel.setName(name);
|
||||
scriptModel.setNodeIds(nodeIds);
|
||||
scriptModel.setDescription(description);
|
||||
scriptModel.setDefArgs(defArgs);
|
||||
scriptModel.setDefArgs(CommandParam.checkStr(defArgs));
|
||||
|
||||
Assert.hasText(scriptModel.getContext(), "内容为空");
|
||||
//
|
||||
|
@ -35,6 +35,7 @@ import io.jpom.permission.ClassFeature;
|
||||
import io.jpom.permission.Feature;
|
||||
import io.jpom.permission.MethodFeature;
|
||||
import io.jpom.permission.SystemPermission;
|
||||
import io.jpom.script.CommandParam;
|
||||
import io.jpom.service.node.command.CommandExecLogService;
|
||||
import io.jpom.service.node.command.CommandService;
|
||||
import io.jpom.service.user.TriggerTokenLogServer;
|
||||
@ -48,7 +49,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -104,7 +104,7 @@ public class CommandInfoController extends BaseServerController {
|
||||
*/
|
||||
@RequestMapping(value = "edit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.EDIT)
|
||||
public JsonMessage<Object> edit(@RequestBody JSONObject data) {
|
||||
public JsonMessage<Object> edit(@RequestBody JSONObject data, HttpServletRequest request) {
|
||||
String name = data.getString("name");
|
||||
String command = data.getString("command");
|
||||
String desc = data.getString("desc");
|
||||
@ -121,22 +121,13 @@ public class CommandInfoController extends BaseServerController {
|
||||
commandModel.setSshIds(data.getString("sshIds"));
|
||||
commandModel.setAutoExecCron(autoExecCron);
|
||||
//
|
||||
if (StrUtil.isNotEmpty(defParams)) {
|
||||
List<CommandModel.CommandParam> params = CommandModel.params(defParams);
|
||||
if (params == null) {
|
||||
commandModel.setDefParams(StrUtil.EMPTY);
|
||||
} else {
|
||||
commandModel.setDefParams(JSONObject.toJSONString(params));
|
||||
}
|
||||
} else {
|
||||
commandModel.setDefParams(StrUtil.EMPTY);
|
||||
}
|
||||
commandModel.setDefParams(CommandParam.checkStr(defParams));
|
||||
|
||||
if (StrUtil.isEmpty(id)) {
|
||||
commandService.insert(commandModel);
|
||||
} else {
|
||||
commandModel.setId(id);
|
||||
commandService.updateById(commandModel, getRequest());
|
||||
commandService.updateById(commandModel, request);
|
||||
}
|
||||
return JsonMessage.success("操作成功");
|
||||
}
|
||||
|
@ -22,15 +22,11 @@
|
||||
*/
|
||||
package io.jpom.model.data;
|
||||
|
||||
import io.jpom.model.BaseJsonModel;
|
||||
import io.jpom.model.BaseWorkspaceModel;
|
||||
import io.jpom.util.StringUtil;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import top.jpom.h2db.TableName;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指令信息
|
||||
*
|
||||
@ -53,10 +49,6 @@ public class CommandModel extends BaseWorkspaceModel {
|
||||
* 指令内容
|
||||
*/
|
||||
private String command;
|
||||
/**
|
||||
* 命令类型,0-shell,1-powershell
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 命令默认参数
|
||||
*/
|
||||
@ -73,21 +65,4 @@ public class CommandModel extends BaseWorkspaceModel {
|
||||
* 触发器 token
|
||||
*/
|
||||
private String triggerToken;
|
||||
|
||||
public List<CommandParam> params() {
|
||||
return params(getDefParams());
|
||||
}
|
||||
|
||||
public static List<CommandParam> params(String defParams) {
|
||||
return StringUtil.jsonConvertArray(defParams, CommandParam.class);
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class CommandParam extends BaseJsonModel {
|
||||
/**
|
||||
* 参数值
|
||||
*/
|
||||
private String value;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
package io.jpom.model.node;
|
||||
|
||||
import io.jpom.model.BaseNodeModel;
|
||||
import io.jpom.script.CommandParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import top.jpom.h2db.TableName;
|
||||
@ -79,4 +80,8 @@ public class ScriptCacheModel extends BaseNodeModel {
|
||||
public void dataId(String id) {
|
||||
setScriptId(id);
|
||||
}
|
||||
|
||||
public void setDefArgs(String defArgs) {
|
||||
this.defArgs = CommandParam.convertToParam(defArgs);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import io.jpom.JpomApplication;
|
||||
import io.jpom.common.Const;
|
||||
import io.jpom.model.BaseWorkspaceModel;
|
||||
import io.jpom.script.CommandParam;
|
||||
import io.jpom.system.ExtConfigBean;
|
||||
import io.jpom.util.CommandUtil;
|
||||
import io.jpom.util.FileUtils;
|
||||
@ -76,6 +77,9 @@ public class ScriptModel extends BaseWorkspaceModel {
|
||||
*/
|
||||
private String triggerToken;
|
||||
|
||||
public void setDefArgs(String defArgs) {
|
||||
this.defArgs = CommandParam.convertToParam(defArgs);
|
||||
}
|
||||
|
||||
public File scriptPath() {
|
||||
return scriptPath(getId());
|
||||
|
@ -35,7 +35,6 @@ import cn.hutool.db.Entity;
|
||||
import cn.hutool.extra.ssh.ChannelType;
|
||||
import cn.hutool.extra.ssh.JschUtil;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.jcraft.jsch.ChannelExec;
|
||||
import io.jpom.common.BaseServerController;
|
||||
import io.jpom.cron.CronUtils;
|
||||
@ -46,6 +45,7 @@ import io.jpom.model.data.CommandExecLogModel;
|
||||
import io.jpom.model.data.CommandModel;
|
||||
import io.jpom.model.data.SshModel;
|
||||
import io.jpom.model.user.UserModel;
|
||||
import io.jpom.script.CommandParam;
|
||||
import io.jpom.service.ITriggerToken;
|
||||
import io.jpom.service.h2db.BaseWorkspaceService;
|
||||
import io.jpom.service.node.ssh.SshService;
|
||||
@ -63,7 +63,6 @@ import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 命令管理
|
||||
@ -199,12 +198,11 @@ public class CommandService extends BaseWorkspaceService<CommandModel> implement
|
||||
*/
|
||||
public String executeBatch(CommandModel commandModel, String params, String nodes, int triggerExecType) {
|
||||
Assert.notNull(commandModel, "没有对应对命令");
|
||||
List<CommandModel.CommandParam> commandParams = CommandModel.params(params);
|
||||
List<String> sshIds = StrUtil.split(nodes, StrUtil.COMMA, true, true);
|
||||
Assert.notEmpty(sshIds, "请选择 ssh 节点");
|
||||
String batchId = IdUtil.fastSimpleUUID();
|
||||
for (String sshId : sshIds) {
|
||||
this.executeItem(commandModel, commandParams, sshId, batchId, triggerExecType);
|
||||
this.executeItem(commandModel, params, sshId, batchId, triggerExecType);
|
||||
}
|
||||
return batchId;
|
||||
}
|
||||
@ -217,7 +215,7 @@ public class CommandService extends BaseWorkspaceService<CommandModel> implement
|
||||
* @param sshId ssh id
|
||||
* @param batchId 批次ID
|
||||
*/
|
||||
private void executeItem(CommandModel commandModel, List<CommandModel.CommandParam> commandParams, String sshId, String batchId, int triggerExecType) {
|
||||
private void executeItem(CommandModel commandModel, String commandParams, String sshId, String batchId, int triggerExecType) {
|
||||
SshModel sshModel = sshService.getByKey(sshId, false);
|
||||
|
||||
CommandExecLogModel commandExecLogModel = new CommandExecLogModel();
|
||||
@ -232,13 +230,7 @@ public class CommandService extends BaseWorkspaceService<CommandModel> implement
|
||||
}
|
||||
commandExecLogModel.setStatus(CommandExecLogModel.Status.ING.getCode());
|
||||
// 拼接参数
|
||||
String commandParamsLine;
|
||||
if (commandParams != null) {
|
||||
commandExecLogModel.setParams(JSONObject.toJSONString(commandParams));
|
||||
commandParamsLine = commandParams.stream().map(CommandModel.CommandParam::getValue).collect(Collectors.joining(StrUtil.SPACE));
|
||||
} else {
|
||||
commandParamsLine = StrUtil.EMPTY;
|
||||
}
|
||||
String commandParamsLine = CommandParam.toCommandLine(commandParams);
|
||||
commandExecLogService.insert(commandExecLogModel);
|
||||
|
||||
ThreadUtil.execute(() -> {
|
||||
@ -372,7 +364,6 @@ public class CommandService extends BaseWorkspaceService<CommandModel> implement
|
||||
update.setId(exits.getId());
|
||||
update.setCommand(data.getCommand());
|
||||
update.setDesc(data.getDesc());
|
||||
update.setType(data.getType());
|
||||
update.setDefParams(data.getDefParams());
|
||||
update.setAutoExecCron(data.getAutoExecCron());
|
||||
super.updateById(update);
|
||||
|
@ -36,6 +36,7 @@ import io.jpom.common.JsonMessage;
|
||||
import io.jpom.model.EnvironmentMapBuilder;
|
||||
import io.jpom.model.script.ScriptModel;
|
||||
import io.jpom.script.BaseRunScript;
|
||||
import io.jpom.script.CommandParam;
|
||||
import io.jpom.service.system.WorkspaceEnvVarService;
|
||||
import io.jpom.system.ExtConfigBean;
|
||||
import io.jpom.util.CommandUtil;
|
||||
@ -79,7 +80,7 @@ public class ServerScriptProcessBuilder extends BaseRunScript implements Runnabl
|
||||
//
|
||||
String script = FileUtil.getAbsolutePath(scriptFile);
|
||||
processBuilder = new ProcessBuilder();
|
||||
List<String> command = StrUtil.splitTrim(args, StrUtil.SPACE);
|
||||
List<String> command = CommandParam.toCommandList(args);
|
||||
command.add(0, script);
|
||||
CommandUtil.paddingPrefix(command);
|
||||
log.debug(CollUtil.join(command, StrUtil.SPACE));
|
||||
|
@ -27,6 +27,7 @@ import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import io.jpom.common.BaseServerController;
|
||||
import io.jpom.common.Const;
|
||||
import io.jpom.common.JsonMessage;
|
||||
import io.jpom.model.script.ScriptExecuteLogModel;
|
||||
import io.jpom.model.script.ScriptModel;
|
||||
import io.jpom.model.user.UserModel;
|
||||
@ -88,7 +89,10 @@ public class ServerScriptHandler extends BaseProxyHandler {
|
||||
json.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG);
|
||||
json.put("executeId", executeId);
|
||||
ServerScriptProcessBuilder.addWatcher(scriptModel, executeId, args, session);
|
||||
this.sendMsg(session, json.toString());
|
||||
JsonMessage<String> jsonMessage = new JsonMessage<>(200, "开始执行");
|
||||
JSONObject jsonObject = jsonMessage.toJson();
|
||||
jsonObject.putAll(json);
|
||||
this.sendMsg(session, jsonObject.toString());
|
||||
break;
|
||||
}
|
||||
case stop: {
|
||||
|
@ -15,3 +15,4 @@ DROP,DOCKER_INFO,failureMsg
|
||||
DROP,USEROPERATELOGV1,reqId
|
||||
ADD,USEROPERATELOGV1,workspaceName,String,50,,工作空间名
|
||||
ADD,USEROPERATELOGV1,username,String,50,,用户名
|
||||
DROP,COMMAND_INFO,type,
|
||||
|
|
@ -287,7 +287,6 @@ COMMAND_INFO,workspaceId,String,50,,true,false,所属工作空间,
|
||||
COMMAND_INFO,name,String,100,,false,false,命令名称,
|
||||
COMMAND_INFO,desc,String,500,,false,false,命令描述,
|
||||
COMMAND_INFO,command,TEXT,,,false,false,指令内容,
|
||||
COMMAND_INFO,type,Integer,,0,false,false,命令类型,0-shell,1-powershell,
|
||||
COMMAND_INFO,defParams,TEXT,,,false,false,命令参数,
|
||||
COMMAND_INFO,sshIds,TEXT,,,false,false,绑定的ssh id,
|
||||
COMMAND_INFO,autoExecCron,String,100,,false,false,自动执行表达式,
|
||||
|
|
@ -16,10 +16,29 @@
|
||||
</div>
|
||||
|
||||
<!--远程下载 -->
|
||||
<a-modal destroyOnClose v-model="editArgs" title="添加运行参数" @ok="startExecution" @cancel="this.editArgs = false" :maskClosable="false">
|
||||
<a-form-model :model="temp" :label-col="{ span: 5 }" :wrapper-col="{ span: 24 }" ref="ruleForm">
|
||||
<a-form-model-item label="执行参数" prop="args">
|
||||
<a-input v-model="temp.args" placeholder="执行参数,没有参数可以不填写" />
|
||||
<a-modal destroyOnClose v-model="editArgs" title="添加运行参数" @ok="startExecution" :maskClosable="false">
|
||||
<a-form-model :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" ref="ruleForm">
|
||||
<a-form-model-item label="命令参数" :help="`${commandParams.length ? '所有参数将拼接成字符串以空格分隔形式执行脚本,需要注意参数顺序和未填写值的参数将自动忽略' : ''}`">
|
||||
<a-row v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" :placeholder="`参数值 ${item.desc ? ',' + item.desc : ''}`">
|
||||
<template slot="suffix">
|
||||
<a-tooltip v-if="item.desc" :title="item.desc">
|
||||
<a-icon type="info-circle" style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
|
||||
<a-col v-if="!item.desc" :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon type="minus-circle" @click="() => commandParams.splice(index, 1)" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
@ -52,8 +71,9 @@ export default {
|
||||
scriptStatus: 0,
|
||||
editArgs: false,
|
||||
temp: {
|
||||
args: "",
|
||||
// args: "",
|
||||
},
|
||||
commandParams: [],
|
||||
// 日志内容
|
||||
// logContext: "loading ...",
|
||||
btnLoading: true,
|
||||
@ -67,7 +87,11 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.initWebSocket();
|
||||
this.temp.args = this.defArgs;
|
||||
if (typeof this.defArgs === "string" && this.defArgs) {
|
||||
this.commandParams = JSON.parse(this.defArgs);
|
||||
} else {
|
||||
this.commandParams = [];
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.socket) {
|
||||
@ -145,7 +169,7 @@ export default {
|
||||
const data = {
|
||||
op: op,
|
||||
scriptId: this.scriptId,
|
||||
args: this.temp.args,
|
||||
args: JSON.stringify(this.commandParams),
|
||||
executeId: this.temp.executeId,
|
||||
};
|
||||
this.socket.send(JSON.stringify(data));
|
||||
|
@ -53,8 +53,28 @@
|
||||
<code-editor v-model="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="默认参数" prop="defArgs">
|
||||
<!-- <a-form-model-item label="默认参数" prop="defArgs">
|
||||
<a-input v-model="temp.defArgs" placeholder="默认参数" />
|
||||
</a-form-model-item> -->
|
||||
<a-form-model-item label="默认参数">
|
||||
<div v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}描述`" v-model="item.desc" placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" />
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" />
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
</div>
|
||||
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="定时执行" prop="autoExecCron">
|
||||
<a-auto-complete v-model="temp.autoExecCron" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])" option-label-prop="value">
|
||||
@ -118,6 +138,7 @@ export default {
|
||||
name: [{ required: true, message: "Please input Script name", trigger: "blur" }],
|
||||
context: [{ required: true, message: "Please input Script context", trigger: "blur" }],
|
||||
},
|
||||
commandParams: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -144,14 +165,13 @@ export default {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
parseTime(v) {
|
||||
return parseTime(v);
|
||||
},
|
||||
parseTime,
|
||||
// 添加
|
||||
handleAdd() {
|
||||
this.temp = {
|
||||
type: "add",
|
||||
};
|
||||
this.commandParams = [];
|
||||
this.editScriptVisible = true;
|
||||
},
|
||||
// 修改
|
||||
@ -161,6 +181,7 @@ export default {
|
||||
nodeId: this.node.id,
|
||||
}).then((res) => {
|
||||
this.temp = Object.assign({}, res.data);
|
||||
this.commandParams = this.temp.defArgs ? JSON.parse(this.temp.defArgs) : [];
|
||||
//
|
||||
this.editScriptVisible = true;
|
||||
});
|
||||
@ -173,6 +194,19 @@ export default {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.commandParams && this.commandParams.length > 0) {
|
||||
for (let i = 0; i < this.commandParams.length; i++) {
|
||||
if (!this.commandParams[i].desc) {
|
||||
this.$notification.error({
|
||||
message: "请填写第" + (i + 1) + "个参数的描述",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.temp.defArgs = JSON.stringify(this.commandParams);
|
||||
} else {
|
||||
this.temp.defArgs = "";
|
||||
}
|
||||
// 检验表单
|
||||
this.$refs["editScriptForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
|
@ -83,8 +83,28 @@
|
||||
<code-editor v-model="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="默认参数" prop="defArgs">
|
||||
<!-- <a-form-model-item label="默认参数" prop="defArgs">
|
||||
<a-input v-model="temp.defArgs" placeholder="默认参数" />
|
||||
</a-form-model-item> -->
|
||||
<a-form-model-item label="默认参数">
|
||||
<div v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}描述`" v-model="item.desc" placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" />
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" />
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
</div>
|
||||
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="定时执行" prop="autoExecCron">
|
||||
<a-auto-complete v-model="temp.autoExecCron" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])" option-label-prop="value">
|
||||
@ -231,6 +251,7 @@ export default {
|
||||
context: [{ required: true, message: "Please input Script context", trigger: "blur" }],
|
||||
},
|
||||
triggerVisible: false,
|
||||
commandParams: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -263,9 +284,7 @@ export default {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
parseTime(v) {
|
||||
return parseTime(v);
|
||||
},
|
||||
parseTime,
|
||||
// 修改
|
||||
handleEdit(record) {
|
||||
itemScript({
|
||||
@ -275,6 +294,7 @@ export default {
|
||||
if (res.code === 200) {
|
||||
this.temp = Object.assign({}, res.data);
|
||||
this.temp.nodeId = record.nodeId;
|
||||
this.commandParams = this.temp.defArgs ? JSON.parse(this.temp.defArgs) : [];
|
||||
//
|
||||
this.editScriptVisible = true;
|
||||
}
|
||||
@ -293,6 +313,19 @@ export default {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
if (this.commandParams && this.commandParams.length > 0) {
|
||||
for (let i = 0; i < this.commandParams.length; i++) {
|
||||
if (!this.commandParams[i].desc) {
|
||||
this.$notification.error({
|
||||
message: "请填写第" + (i + 1) + "个参数的描述",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.temp.defArgs = JSON.stringify(this.commandParams);
|
||||
} else {
|
||||
this.temp.defArgs = "";
|
||||
}
|
||||
// 提交数据
|
||||
editScript(this.temp).then((res) => {
|
||||
if (res.code === 200) {
|
||||
|
@ -15,10 +15,32 @@
|
||||
</div>
|
||||
|
||||
<!--远程下载 -->
|
||||
<a-modal destroyOnClose v-model="editArgs" title="添加运行参数" @ok="startExecution" @cancel="this.editArgs = false" :maskClosable="false">
|
||||
<a-form-model :model="temp" :label-col="{ span: 5 }" :wrapper-col="{ span: 24 }" ref="ruleForm">
|
||||
<a-form-model-item label="执行参数" prop="args">
|
||||
<a-modal destroyOnClose v-model="editArgs" title="添加运行参数" @ok="startExecution" :maskClosable="false">
|
||||
<a-form-model :model="temp" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" ref="ruleForm">
|
||||
<!-- <a-form-model-item label="执行参数" prop="args">
|
||||
<a-input v-model="temp.args" placeholder="执行参数,没有参数可以不填写" />
|
||||
</a-form-model-item> -->
|
||||
<a-form-model-item label="命令参数" :help="`${commandParams.length ? '所有参数将拼接成字符串以空格分隔形式执行脚本,需要注意参数顺序和未填写值的参数将自动忽略' : ''}`">
|
||||
<a-row v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" :placeholder="`参数值 ${item.desc ? ',' + item.desc : ''}`">
|
||||
<template slot="suffix">
|
||||
<a-tooltip v-if="item.desc" :title="item.desc">
|
||||
<a-icon type="info-circle" style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
|
||||
<a-col v-if="!item.desc" :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon type="minus-circle" @click="() => commandParams.splice(index, 1)" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
@ -47,11 +69,12 @@ export default {
|
||||
scriptStatus: 0,
|
||||
editArgs: false,
|
||||
temp: {
|
||||
args: "",
|
||||
// args: "",
|
||||
},
|
||||
// 日志内容
|
||||
// logContext: "loading ...",
|
||||
btnLoading: true,
|
||||
commandParams: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -62,7 +85,11 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.initWebSocket();
|
||||
this.temp.args = this.defArgs;
|
||||
if (typeof this.defArgs === "string" && this.defArgs) {
|
||||
this.commandParams = JSON.parse(this.defArgs);
|
||||
} else {
|
||||
this.commandParams = [];
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.socket) {
|
||||
@ -139,7 +166,7 @@ export default {
|
||||
const data = {
|
||||
op: op,
|
||||
id: this.id,
|
||||
args: this.temp.args,
|
||||
args: JSON.stringify(this.commandParams),
|
||||
executeId: this.temp.executeId,
|
||||
};
|
||||
this.socket.send(JSON.stringify(data));
|
||||
|
@ -90,8 +90,28 @@
|
||||
<code-editor v-model="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="默认参数" prop="defArgs">
|
||||
<!-- <a-form-model-item label="默认参数" prop="defArgs">
|
||||
<a-input v-model="temp.defArgs" placeholder="默认参数" />
|
||||
</a-form-model-item> -->
|
||||
<a-form-model-item label="默认参数">
|
||||
<div v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}描述`" v-model="item.desc" placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" />
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" />
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
</div>
|
||||
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="定时执行" prop="autoExecCron">
|
||||
<a-auto-complete v-model="temp.autoExecCron" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])" option-label-prop="value">
|
||||
@ -259,6 +279,7 @@ export default {
|
||||
syncToWorkspaceVisible: false,
|
||||
workspaceList: [],
|
||||
triggerVisible: false,
|
||||
commandParams: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -304,15 +325,16 @@ export default {
|
||||
},
|
||||
createScript() {
|
||||
this.temp = {};
|
||||
this.commandParams = [];
|
||||
this.editScriptVisible = true;
|
||||
this.getAllNodeList();
|
||||
},
|
||||
// 修改
|
||||
handleEdit(record) {
|
||||
this.temp = Object.assign({}, record);
|
||||
// record;
|
||||
//
|
||||
// this.temp.;
|
||||
|
||||
this.commandParams = record.defArgs ? JSON.parse(record.defArgs) : [];
|
||||
|
||||
this.temp = { ...this.temp, chooseNode: record.nodeIds ? record.nodeIds.split(",") : [] };
|
||||
this.editScriptVisible = true;
|
||||
this.getAllNodeList();
|
||||
@ -324,6 +346,19 @@ export default {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
if (this.commandParams && this.commandParams.length > 0) {
|
||||
for (let i = 0; i < this.commandParams.length; i++) {
|
||||
if (!this.commandParams[i].desc) {
|
||||
this.$notification.error({
|
||||
message: "请填写第" + (i + 1) + "个参数的描述",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.temp.defArgs = JSON.stringify(this.commandParams);
|
||||
} else {
|
||||
this.temp.defArgs = "";
|
||||
}
|
||||
// 提交数据
|
||||
this.temp.nodeIds = this.temp?.chooseNode?.join(",");
|
||||
editScript(this.temp).then((res) => {
|
||||
|
@ -83,16 +83,24 @@
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item label="默认参数">
|
||||
<div class="params-item" v-for="(item, index) in commandParams" :key="item.key">
|
||||
<div class="item-info">
|
||||
<a-input addon-before="参数值" v-model="item.value" placeholder="参数值" />
|
||||
</div>
|
||||
<div class="item-icon" @click="handleDeleteParam(index)">
|
||||
<a-icon type="minus-circle" style="color: #ff0000" />
|
||||
</div>
|
||||
<div v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}描述`" v-model="item.desc" placeholder="参数描述,参数描述没有实际作用,仅是用于提示参数的含义" />
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" placeholder="参数值,添加默认参数后在手动执行脚本时需要填写参数值" />
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon @click="() => commandParams.splice(index, 1)" type="minus-circle" style="color: #ff0000" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider style="margin: 5px 0" />
|
||||
</div>
|
||||
|
||||
<a-button type="primary" @click="handleAddParam">添加参数</a-button>
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="自动执行" prop="autoExecCron">
|
||||
<a-auto-complete v-model="temp.autoExecCron" placeholder="如果需要定时自动执行则填写,cron 表达式.默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond])" option-label-prop="value">
|
||||
@ -126,16 +134,27 @@
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
|
||||
<a-form-model-item label="命令参数">
|
||||
<div v-for="item in commandParams" :key="item.key">
|
||||
<div class="item-info">
|
||||
<a-input addon-before="参数值" v-model="item.value" placeholder="参数值" />
|
||||
</div>
|
||||
<div class="item-icon" @click="handleDeleteParam(index)">
|
||||
<a-icon type="minus-circle" style="color: #ff0000" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="primary" @click="handleAddParam">添加参数</a-button>
|
||||
<a-form-model-item label="命令参数" :help="`${commandParams.length ? '所有参数将拼接成字符串以空格分隔形式执行脚本,需要注意参数顺序和未填写值的参数将自动忽略' : ''}`">
|
||||
<a-row v-for="(item, index) in commandParams" :key="item.key">
|
||||
<a-col :span="22">
|
||||
<a-input :addon-before="`参数${index + 1}值`" v-model="item.value" :placeholder="`参数值 ${item.desc ? ',' + item.desc : ''}`">
|
||||
<template slot="suffix">
|
||||
<a-tooltip v-if="item.desc" :title="item.desc">
|
||||
<a-icon type="info-circle" style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
|
||||
<a-col v-if="!item.desc" :span="2">
|
||||
<a-row type="flex" justify="center" align="middle">
|
||||
<a-col>
|
||||
<a-icon type="minus-circle" style="color: #ff0000" @click="() => commandParams.splice(index, 1)" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" @click="() => commandParams.push({})">添加参数</a-button>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
@ -327,6 +346,14 @@ export default {
|
||||
}
|
||||
this.formLoading = true;
|
||||
if (this.commandParams && this.commandParams.length > 0) {
|
||||
for (let i = 0; i < this.commandParams.length; i++) {
|
||||
if (!this.commandParams[i].desc) {
|
||||
this.$notification.error({
|
||||
message: "请填写第" + (i + 1) + "个参数的描述",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.temp.defParams = JSON.stringify(this.commandParams);
|
||||
} else {
|
||||
this.temp.defParams = "";
|
||||
@ -423,19 +450,7 @@ export default {
|
||||
this.sshList = res.data || [];
|
||||
});
|
||||
},
|
||||
// 添加命令参数
|
||||
handleAddParam() {
|
||||
this.commandParams.push({});
|
||||
},
|
||||
// 删除命令参数
|
||||
handleDeleteParam(index) {
|
||||
this.commandParams.splice(index, 1);
|
||||
},
|
||||
handleParamChange(check) {
|
||||
if (!check) {
|
||||
this.commandParams = [];
|
||||
}
|
||||
},
|
||||
|
||||
handleExecuteCommandOk() {
|
||||
if (!this.chooseSsh || this.chooseSsh.length <= 0) {
|
||||
this.$notification.error({
|
||||
@ -542,22 +557,4 @@ export default {
|
||||
overflow-y: scroll;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.params-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px #e2e2e2 solid;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
display: inline-block;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
@ -113,20 +113,17 @@ export default {
|
||||
customRender: (text) => {
|
||||
return parseTime(text);
|
||||
},
|
||||
width: 170,
|
||||
width: "170px",
|
||||
},
|
||||
{
|
||||
title: "修改时间",
|
||||
dataIndex: "modifyTimeMillis",
|
||||
|
||||
customRender: (text) => {
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
return parseTime(text);
|
||||
},
|
||||
sorter: true,
|
||||
width: 180,
|
||||
width: "170px",
|
||||
},
|
||||
{ title: "操作", dataIndex: "operation", align: "center", scopedSlots: { customRender: "operation" }, width: "220px" },
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user