mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-12-02 11:58:01 +08:00
脚本日志管理
This commit is contained in:
parent
da4b8396fb
commit
abd246828e
@ -22,17 +22,26 @@
|
||||
*/
|
||||
package io.jpom.controller.script;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import cn.jiangzeyin.common.JsonMessage;
|
||||
import cn.jiangzeyin.common.validator.ValidatorItem;
|
||||
import cn.jiangzeyin.common.validator.ValidatorRule;
|
||||
import cn.jiangzeyin.controller.multipart.MultipartFileBuilder;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.jpom.common.BaseAgentController;
|
||||
import io.jpom.model.data.NodeScriptModel;
|
||||
import io.jpom.service.script.ScriptServer;
|
||||
import io.jpom.socket.ScriptProcessBuilder;
|
||||
import io.jpom.system.AgentConfigBean;
|
||||
import io.jpom.system.ExtConfigBean;
|
||||
import io.jpom.util.CommandUtil;
|
||||
import io.jpom.util.LimitQueue;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -128,4 +137,63 @@ public class ScriptController extends BaseAgentController {
|
||||
scriptServer.addItem(eModel);
|
||||
return JsonMessage.getString(200, "导入成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取的日志
|
||||
*
|
||||
* @param id id
|
||||
* @param executeId 执行ID
|
||||
* @param line 需要获取的行号
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String getNowLog(@ValidatorItem() String id,
|
||||
@ValidatorItem() String executeId,
|
||||
@ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line) {
|
||||
NodeScriptModel item = scriptServer.getItem(id);
|
||||
Assert.notNull(item, "没有对应数据");
|
||||
File logFile = item.logFile(executeId);
|
||||
Assert.state(FileUtil.isFile(logFile), "日志文件错误");
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
// 运行中
|
||||
data.put("run", ScriptProcessBuilder.isRun(executeId));
|
||||
// 读取文件
|
||||
int linesInt = Convert.toInt(line, 1);
|
||||
LimitQueue<String> lines = new LimitQueue<>(1000);
|
||||
final int[] readCount = {0};
|
||||
FileUtil.readLines(logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line1 -> {
|
||||
readCount[0]++;
|
||||
if (readCount[0] < linesInt) {
|
||||
return;
|
||||
}
|
||||
lines.add(line1);
|
||||
});
|
||||
// 下次应该获取的行数
|
||||
data.put("line", readCount[0] + 1);
|
||||
data.put("getLine", linesInt);
|
||||
data.put("dataLines", lines);
|
||||
return JsonMessage.getString(200, "ok", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日志
|
||||
*
|
||||
* @param id id
|
||||
* @param executeId 执行ID
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "del_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String delLog(@ValidatorItem() String id,
|
||||
@ValidatorItem() String executeId) {
|
||||
NodeScriptModel item = scriptServer.getItem(id);
|
||||
if (item == null) {
|
||||
return JsonMessage.getString(200, "对应的脚本模版已经不存在拉");
|
||||
}
|
||||
Assert.notNull(item, "没有对应数据");
|
||||
File logFile = item.logFile(executeId);
|
||||
boolean fastDel = CommandUtil.systemFastDel(logFile);
|
||||
Assert.state(!fastDel, "删除日志文件失败");
|
||||
return JsonMessage.getString(200, "删除成功");
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
package io.jpom.socket;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
@ -52,6 +53,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @date 2019/4/25
|
||||
*/
|
||||
public class ScriptProcessBuilder implements Runnable {
|
||||
/**
|
||||
* 执行中的缓存
|
||||
*/
|
||||
private static final ConcurrentHashMap<String, ScriptProcessBuilder> FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private final ProcessBuilder processBuilder;
|
||||
@ -79,6 +83,14 @@ public class ScriptProcessBuilder implements Runnable {
|
||||
processBuilder.command(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建执行 并监听
|
||||
*
|
||||
* @param nodeScriptModel 脚本模版
|
||||
* @param executeId 执行ID
|
||||
* @param args 参数
|
||||
* @param session 会话
|
||||
*/
|
||||
public static void addWatcher(NodeScriptModel nodeScriptModel, String executeId, String args, Session session) {
|
||||
ScriptProcessBuilder scriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.computeIfAbsent(executeId, file1 -> {
|
||||
ScriptProcessBuilder scriptProcessBuilder1 = new ScriptProcessBuilder(nodeScriptModel, executeId, args);
|
||||
@ -99,6 +111,21 @@ public class ScriptProcessBuilder implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否还在执行中
|
||||
*
|
||||
* @param executeId 执行id
|
||||
* @return true 还在执行
|
||||
*/
|
||||
public static boolean isRun(String executeId) {
|
||||
return FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.containsKey(executeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭会话
|
||||
*
|
||||
* @param session 会话
|
||||
*/
|
||||
public static void stopWatcher(Session session) {
|
||||
Collection<ScriptProcessBuilder> scriptProcessBuilders = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.values();
|
||||
for (ScriptProcessBuilder scriptProcessBuilder : scriptProcessBuilders) {
|
||||
@ -123,6 +150,7 @@ public class ScriptProcessBuilder implements Runnable {
|
||||
public void run() {
|
||||
//初始化ProcessBuilder对象
|
||||
try {
|
||||
this.handle("start execute:" + DateUtil.now());
|
||||
process = processBuilder.start();
|
||||
{
|
||||
inputStream = process.getInputStream();
|
||||
@ -133,6 +161,7 @@ public class ScriptProcessBuilder implements Runnable {
|
||||
JSONObject jsonObject = jsonMessage.toJson();
|
||||
jsonObject.put("op", ConsoleCommandOp.stop.name());
|
||||
this.end(jsonObject.toString());
|
||||
this.handle("execute done:" + waitFor + " time:" + DateUtil.now());
|
||||
} catch (Exception e) {
|
||||
DefaultSystemLog.getLog().error("执行异常", e);
|
||||
this.end("执行异常:" + e.getMessage());
|
||||
|
@ -72,6 +72,7 @@ public enum ClassFeature {
|
||||
PROJECT_CONSOLE("项目控制台", ClassFeature.NODE),
|
||||
JDK_LIST("JDK管理", ClassFeature.NODE),
|
||||
NODE_SCRIPT("脚本模板", ClassFeature.NODE),
|
||||
NODE_SCRIPT_LOG("脚本模板日志", ClassFeature.NODE),
|
||||
TOMCAT("Tomcat", ClassFeature.NODE),
|
||||
TOMCAT_FILE("Tomcat file", ClassFeature.NODE),
|
||||
TOMCAT_LOG("Tomcat log", ClassFeature.NODE),
|
||||
|
@ -172,6 +172,8 @@ public enum NodeUrl {
|
||||
Script_List("/script/list.json"),
|
||||
Script_Item("/script/item.json"),
|
||||
Script_Save("/script/save.json"),
|
||||
SCRIPT_LOG("/script/log"),
|
||||
SCRIPT_DEL_LOG("/script/del_log"),
|
||||
Script_Upload("/script/upload.json"),
|
||||
Script_Del("/script/del.json"),
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
package io.jpom.controller.node.script;
|
||||
|
||||
import cn.jiangzeyin.common.JsonMessage;
|
||||
import cn.jiangzeyin.common.validator.ValidatorItem;
|
||||
import io.jpom.common.BaseServerController;
|
||||
import io.jpom.common.forward.NodeForward;
|
||||
import io.jpom.common.forward.NodeUrl;
|
||||
import io.jpom.model.PageResultDto;
|
||||
import io.jpom.model.data.NodeModel;
|
||||
import io.jpom.model.data.ScriptExecuteLogModel;
|
||||
import io.jpom.plugin.ClassFeature;
|
||||
import io.jpom.plugin.Feature;
|
||||
import io.jpom.plugin.MethodFeature;
|
||||
import io.jpom.service.node.script.ScriptExecuteLogServer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author bwcx_jzy
|
||||
* @since 2021/12/24
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/node/script_log")
|
||||
@Feature(cls = ClassFeature.NODE_SCRIPT_LOG)
|
||||
public class ScriptLogController extends BaseServerController {
|
||||
|
||||
private final ScriptExecuteLogServer scriptExecuteLogServer;
|
||||
|
||||
public ScriptLogController(ScriptExecuteLogServer scriptExecuteLogServer) {
|
||||
this.scriptExecuteLogServer = scriptExecuteLogServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* get script log list
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String scriptList() {
|
||||
PageResultDto<ScriptExecuteLogModel> pageResultDto = scriptExecuteLogServer.listPageNode(getRequest());
|
||||
return JsonMessage.getString(200, "", pageResultDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查日志
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.LIST)
|
||||
public String log() {
|
||||
NodeModel node = getNode();
|
||||
return NodeForward.request(node, getRequest(), NodeUrl.SCRIPT_LOG).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日志
|
||||
*
|
||||
* @param id 模版ID
|
||||
* @param executeId 日志ID
|
||||
* @return json
|
||||
*/
|
||||
@RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Feature(method = MethodFeature.DEL)
|
||||
public String del(@ValidatorItem String id, String executeId) {
|
||||
NodeModel node = getNode();
|
||||
ScriptExecuteLogModel scriptExecuteLogModel = new ScriptExecuteLogModel();
|
||||
scriptExecuteLogModel.setId(executeId);
|
||||
scriptExecuteLogModel.setScriptId(id);
|
||||
scriptExecuteLogModel.setNodeId(node.getId());
|
||||
ScriptExecuteLogModel executeLogModel = scriptExecuteLogServer.queryByBean(scriptExecuteLogModel);
|
||||
Assert.notNull(executeLogModel, "没有对应的执行日志");
|
||||
JsonMessage<Object> request = NodeForward.request(node, getRequest(), NodeUrl.SCRIPT_DEL_LOG);
|
||||
if (request.getCode() == HttpStatus.OK.value()) {
|
||||
scriptExecuteLogServer.delByKey(executeId);
|
||||
}
|
||||
return request.toString();
|
||||
}
|
||||
}
|
@ -37,6 +37,19 @@ public class ScriptExecuteLogModel extends BaseNodeModel {
|
||||
*/
|
||||
private String scriptId;
|
||||
|
||||
/**
|
||||
* 脚本名称
|
||||
*/
|
||||
private String scriptName;
|
||||
|
||||
public String getScriptName() {
|
||||
return scriptName;
|
||||
}
|
||||
|
||||
public void setScriptName(String scriptName) {
|
||||
this.scriptName = scriptName;
|
||||
}
|
||||
|
||||
public String getScriptId() {
|
||||
return scriptId;
|
||||
}
|
||||
|
@ -1,7 +1,17 @@
|
||||
package io.jpom.service.node.script;
|
||||
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import cn.jiangzeyin.common.DefaultSystemLog;
|
||||
import cn.jiangzeyin.common.JsonMessage;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.jpom.common.forward.NodeForward;
|
||||
import io.jpom.common.forward.NodeUrl;
|
||||
import io.jpom.model.data.NodeModel;
|
||||
import io.jpom.model.data.ScriptExecuteLogModel;
|
||||
import io.jpom.service.h2db.BaseWorkspaceService;
|
||||
import io.jpom.service.h2db.BaseNodeService;
|
||||
import io.jpom.service.node.NodeService;
|
||||
import io.jpom.service.system.WorkspaceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@ -11,10 +21,48 @@ import org.springframework.stereotype.Service;
|
||||
* @since 2021/12/12
|
||||
*/
|
||||
@Service
|
||||
public class ScriptExecuteLogServer extends BaseWorkspaceService<ScriptExecuteLogModel> {
|
||||
public class ScriptExecuteLogServer extends BaseNodeService<ScriptExecuteLogModel> {
|
||||
|
||||
protected ScriptExecuteLogServer(NodeService nodeService,
|
||||
WorkspaceService workspaceService) {
|
||||
super(nodeService, workspaceService, "脚本模版日志");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] clearTimeColumns() {
|
||||
return new String[]{"createTimeMillis"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getItem(NodeModel nodeModel, String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONArray getLitDataArray(NodeModel nodeModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncAllNode() {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeClearImpl(int h2DbLogStorageCount) {
|
||||
super.autoLoopClear("createTimeMillis", h2DbLogStorageCount,
|
||||
null,
|
||||
executeLogModel -> {
|
||||
try {
|
||||
NodeModel nodeModel = nodeService.getByKey(executeLogModel.getNodeId());
|
||||
JsonMessage<Object> jsonMessage = NodeForward.requestBySys(nodeModel, NodeUrl.SCRIPT_DEL_LOG,
|
||||
"id", executeLogModel.getScriptId(), "executeId", executeLogModel.getId());
|
||||
if (jsonMessage.getCode() != HttpStatus.HTTP_OK) {
|
||||
DefaultSystemLog.getLog().info(jsonMessage.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
DefaultSystemLog.getLog().error("自动清除数据错误", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -85,16 +85,17 @@ public class ScriptHandler extends BaseProxyHandler {
|
||||
ScriptServer scriptServer = SpringUtil.getBean(ScriptServer.class);
|
||||
//
|
||||
try {
|
||||
BaseServerController.resetInfo(userModel);
|
||||
//
|
||||
ScriptModel scriptModel = new ScriptModel();
|
||||
scriptModel.setId(dataItem.getId());
|
||||
scriptModel.setLastRunUser(userModel.getId());
|
||||
scriptServer.update(scriptModel);
|
||||
//
|
||||
BaseServerController.resetInfo(userModel);
|
||||
ScriptExecuteLogModel scriptExecuteLogModel = new ScriptExecuteLogModel();
|
||||
scriptExecuteLogModel.setScriptId(dataItem.getScriptId());
|
||||
scriptExecuteLogModel.setNodeId(nodeInfo.getId());
|
||||
scriptExecuteLogModel.setScriptName(dataItem.getName());
|
||||
scriptExecuteLogModel.setWorkspaceId(nodeInfo.getWorkspaceId());
|
||||
logServer.insert(scriptExecuteLogModel);
|
||||
return scriptExecuteLogModel.getId();
|
||||
|
@ -33,6 +33,10 @@
|
||||
{
|
||||
"id": "script",
|
||||
"title": "脚本模板"
|
||||
},
|
||||
{
|
||||
"id": "script-log",
|
||||
"title": "脚本执行记录"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -18,3 +18,6 @@ ALTER TABLE COMMAND_EXEC_LOG
|
||||
-- 分发状态
|
||||
ALTER TABLE OUT_GIVING
|
||||
ADD IF NOT EXISTS `status` int default '0' comment '状态{0: 未分发; 1: 分发中; 2: 分发结束}';
|
||||
|
||||
ALTER TABLE SCRIPT_EXECUTE_LOG
|
||||
ADD IF NOT EXISTS scriptName VARCHAR (100) comment '脚本名称';
|
||||
|
@ -256,7 +256,7 @@ export function getScriptList(params) {
|
||||
}
|
||||
|
||||
/**
|
||||
* script 列表
|
||||
* script 服务端中的所有列表
|
||||
*/
|
||||
export function getScriptListAll(params) {
|
||||
return axios({
|
||||
@ -266,6 +266,36 @@ export function getScriptListAll(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 脚本模版日志列表
|
||||
export function getScriptLogList(params) {
|
||||
return axios({
|
||||
url: "/node/script_log/list",
|
||||
method: "post",
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除执行记录
|
||||
export function scriptDel(params) {
|
||||
return axios({
|
||||
url: "/node/script_log/del",
|
||||
method: "post",
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
//执行记录 详情
|
||||
export function scriptLog(params) {
|
||||
return axios({
|
||||
url: "/node/script_log/log",
|
||||
method: "post",
|
||||
data: params,
|
||||
headers: {
|
||||
tip: "no",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Script 编辑
|
||||
* @param {nodeId, id, name, path, port, appBase} params
|
||||
|
@ -7,15 +7,15 @@
|
||||
<a-sub-menu v-if="menu.childs" :key="menu.id" :class="menu.id">
|
||||
<span slot="title">
|
||||
<a-icon :type="menu.icon_v3" />
|
||||
<span>{{menu.title}}</span>
|
||||
<span>{{ menu.title }}</span>
|
||||
</span>
|
||||
<a-menu-item v-for="subMenu in menu.childs" :key="subMenu.id" @click="handleMenuClick(subMenu.id, subMenu.pId)" :class="subMenu.id">
|
||||
<span>{{subMenu.title}}</span>
|
||||
<span>{{ subMenu.title }}</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-menu-item v-else :key="menu.id" @click="handleMenuClick(menu.id)">
|
||||
<a-icon :type="menu.icon_v3" />
|
||||
<span>{{menu.title}}</span>
|
||||
<span>{{ menu.title }}</span>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
@ -28,6 +28,7 @@
|
||||
<recover v-if="currentId === 'projectRecover'" :node="node" />
|
||||
<tomcat v-if="currentId === 'tomcatManage'" :node="node" />
|
||||
<script-template v-if="currentId === 'script'" :node="node" />
|
||||
<script-log v-if="currentId === 'script-log'" :node="node" />
|
||||
<nginx-list v-if="currentId === 'nginxList'" :node="node" />
|
||||
<cert v-if="currentId === 'certificate'" :node="node" />
|
||||
<white-list v-if="currentId === 'whitelistDirectory'" :node="node" />
|
||||
@ -39,22 +40,23 @@
|
||||
</a-layout>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getNodeMenu } from '../../../api/menu';
|
||||
import Welcome from './welcome';
|
||||
import ProjectList from './project/project-list';
|
||||
import JdkList from './project/jdk-list';
|
||||
import Recover from './project/recover-list';
|
||||
import Tomcat from './other/tomcat-list';
|
||||
import ScriptTemplate from './other/script-list';
|
||||
import NginxList from './nginx/list';
|
||||
import Cert from './nginx/cert';
|
||||
import WhiteList from './system/white-list.vue';
|
||||
import Cache from './system/cache';
|
||||
import Log from './system/log.vue';
|
||||
import Upgrade from './system/upgrade.vue';
|
||||
import ConfigFile from './system/config-file.vue';
|
||||
import { GUIDE_NODE_USED_KEY } from '../../../utils/const';
|
||||
import { mapGetters } from "vuex";
|
||||
import { getNodeMenu } from "../../../api/menu";
|
||||
import Welcome from "./welcome";
|
||||
import ProjectList from "./project/project-list";
|
||||
import JdkList from "./project/jdk-list";
|
||||
import Recover from "./project/recover-list";
|
||||
import Tomcat from "./other/tomcat-list";
|
||||
import ScriptTemplate from "@/pages/node/node-layout/other/script-list";
|
||||
import ScriptLog from "@/pages/node/node-layout/other/script-log";
|
||||
import NginxList from "./nginx/list";
|
||||
import Cert from "./nginx/cert";
|
||||
import WhiteList from "./system/white-list.vue";
|
||||
import Cache from "./system/cache";
|
||||
import Log from "./system/log.vue";
|
||||
import Upgrade from "./system/upgrade.vue";
|
||||
import ConfigFile from "./system/config-file.vue";
|
||||
import { GUIDE_NODE_USED_KEY } from "../../../utils/const";
|
||||
export default {
|
||||
components: {
|
||||
Welcome,
|
||||
@ -69,23 +71,22 @@ export default {
|
||||
Cache,
|
||||
Log,
|
||||
Upgrade,
|
||||
ConfigFile
|
||||
ConfigFile,
|
||||
ScriptLog,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object
|
||||
}
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeMenuList: [],
|
||||
selectedKeys: [this.$route.query.id || 'welcome']
|
||||
}
|
||||
selectedKeys: [this.$route.query.id || "welcome"],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getGuideFlag'
|
||||
]),
|
||||
...mapGetters(["getGuideFlag"]),
|
||||
currentId() {
|
||||
return this.selectedKeys[0];
|
||||
},
|
||||
@ -93,12 +94,13 @@ export default {
|
||||
let keyList = [];
|
||||
// 引导开启且没指定打开某一项菜单时打开系统配置
|
||||
if (this.getGuideFlag && !this.$route.query.pId) {
|
||||
keyList = ['systemConfig']
|
||||
} else if (this.$route.query.pId){ // 打开对应的父级菜单
|
||||
keyList = [this.$route.query.pId]
|
||||
keyList = ["systemConfig"];
|
||||
} else if (this.$route.query.pId) {
|
||||
// 打开对应的父级菜单
|
||||
keyList = [this.$route.query.pId];
|
||||
}
|
||||
return keyList
|
||||
}
|
||||
return keyList;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
getGuideFlag() {
|
||||
@ -114,48 +116,58 @@ export default {
|
||||
methods: {
|
||||
// 页面引导
|
||||
introGuide() {
|
||||
const used = localStorage.getItem(GUIDE_NODE_USED_KEY) === 'true';
|
||||
const used = localStorage.getItem(GUIDE_NODE_USED_KEY) === "true";
|
||||
// 如果要显示引导并且没有使用过
|
||||
if (this.getGuideFlag && !used) {
|
||||
this.$introJs().setOptions({
|
||||
hidePrev: true,
|
||||
steps: [{
|
||||
title: 'Jpom 导航助手',
|
||||
element: document.querySelector('.ant-drawer-title'),
|
||||
intro: '这里是这个节点的名称和节点地址'
|
||||
}, {
|
||||
title: 'Jpom 导航助手',
|
||||
element: document.querySelector('.jpom-node-sider'),
|
||||
intro: '这里是这个节点的侧边栏菜单'
|
||||
}, {
|
||||
title: 'Jpom 导航助手',
|
||||
element: document.querySelector('.jpom-node-content'),
|
||||
intro: '这里是这个节点的主要内容展示区'
|
||||
}, {
|
||||
title: 'Jpom 导航助手',
|
||||
element: document.querySelector('.whitelistDirectory'),
|
||||
intro: '白名单目录是一个配置型菜单,里面配置的内容将会在</p><p><b>项目列表</b></br><b>Nginx 列表</b></br><b>证书管理</b></p>菜单里面作为选择项出现。'
|
||||
}]
|
||||
}).start().onexit(() => {
|
||||
localStorage.setItem(GUIDE_NODE_USED_KEY, 'true');
|
||||
});
|
||||
this.$introJs()
|
||||
.setOptions({
|
||||
hidePrev: true,
|
||||
steps: [
|
||||
{
|
||||
title: "Jpom 导航助手",
|
||||
element: document.querySelector(".ant-drawer-title"),
|
||||
intro: "这里是这个节点的名称和节点地址",
|
||||
},
|
||||
{
|
||||
title: "Jpom 导航助手",
|
||||
element: document.querySelector(".jpom-node-sider"),
|
||||
intro: "这里是这个节点的侧边栏菜单",
|
||||
},
|
||||
{
|
||||
title: "Jpom 导航助手",
|
||||
element: document.querySelector(".jpom-node-content"),
|
||||
intro: "这里是这个节点的主要内容展示区",
|
||||
},
|
||||
{
|
||||
title: "Jpom 导航助手",
|
||||
element: document.querySelector(".whitelistDirectory"),
|
||||
intro: "白名单目录是一个配置型菜单,里面配置的内容将会在</p><p><b>项目列表</b></br><b>Nginx 列表</b></br><b>证书管理</b></p>菜单里面作为选择项出现。",
|
||||
},
|
||||
],
|
||||
})
|
||||
.start()
|
||||
.onexit(() => {
|
||||
localStorage.setItem(GUIDE_NODE_USED_KEY, "true");
|
||||
});
|
||||
return false;
|
||||
}
|
||||
this.$introJs().exit();
|
||||
},
|
||||
// 加载菜单
|
||||
loadNodeMenu() {
|
||||
getNodeMenu(this.node.id).then(res => {
|
||||
getNodeMenu(this.node.id).then((res) => {
|
||||
if (res.code === 200) {
|
||||
const data = res.data.map(item => {
|
||||
const childs = item.childs && item.childs.map(sub => {
|
||||
return {...sub, pId: item.id}
|
||||
})
|
||||
return {...item, childs}
|
||||
})
|
||||
const data = res.data.map((item) => {
|
||||
const childs =
|
||||
item.childs &&
|
||||
item.childs.map((sub) => {
|
||||
return { ...sub, pId: item.id };
|
||||
});
|
||||
return { ...item, childs };
|
||||
});
|
||||
this.nodeMenuList = data;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
// 点击菜单
|
||||
handleMenuClick(id, pId) {
|
||||
@ -164,12 +176,12 @@ export default {
|
||||
query: {
|
||||
...this.$route.query,
|
||||
pId: pId,
|
||||
id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.sider {
|
||||
|
@ -33,13 +33,15 @@
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 编辑区 -->
|
||||
<a-modal v-model="editScriptVisible" title="编辑 Script" @ok="handleEditScriptOk" :maskClosable="false" width="1200px">
|
||||
<a-modal v-model="editScriptVisible" title="编辑 Script" @ok="handleEditScriptOk" :maskClosable="false" width="80vw">
|
||||
<a-form-model ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-model-item label="Script 名称" prop="name">
|
||||
<a-input v-model="temp.name" placeholder="名称" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="Script 内容" prop="context">
|
||||
<code-editor v-model="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor>
|
||||
<div style="height: 40vh; overflow-y: scroll">
|
||||
<code-editor v-model="temp.context" :options="{ mode: 'shell', tabSize: 2, theme: 'abcdef' }"></code-editor>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
@ -65,7 +67,8 @@ import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL,
|
||||
import { parseTime } from "@/utils/time";
|
||||
export default {
|
||||
components: {
|
||||
ScriptConsole,codeEditor
|
||||
ScriptConsole,
|
||||
codeEditor,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
@ -275,10 +278,4 @@ export default {
|
||||
.filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.ant-btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.node-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
|
88
web-vue/src/pages/node/node-layout/other/script-log-view.vue
Normal file
88
web-vue/src/pages/node/node-layout/other/script-log-view.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input id="script-log-textarea" v-model="logText" type="textarea" class="console" readOnly style="resize: none; height: 70vh" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { scriptLog } from "@/api/node-other";
|
||||
export default {
|
||||
props: {
|
||||
temp: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
logTimer: null,
|
||||
logText: "loading...",
|
||||
line: 1,
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.logTimer) {
|
||||
clearInterval(this.logTimer);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.loadData();
|
||||
this.logTimer = setInterval(() => {
|
||||
this.loadData();
|
||||
}, 2000);
|
||||
},
|
||||
// 加载日志内容
|
||||
loadData() {
|
||||
// 加载构建日志
|
||||
const params = {
|
||||
executeId: this.temp.id,
|
||||
id: this.temp.scriptId,
|
||||
nodeId: this.temp.nodeId,
|
||||
line: this.line,
|
||||
};
|
||||
scriptLog(params).then((res) => {
|
||||
if (res.code === 200) {
|
||||
// 停止请求
|
||||
if (res.data.run === false) {
|
||||
clearInterval(this.logTimer);
|
||||
}
|
||||
// 更新日志
|
||||
if (this.logText === "loading...") {
|
||||
this.logText = "";
|
||||
}
|
||||
let lines = res.data.dataLines;
|
||||
lines.forEach((element) => {
|
||||
this.logText += `${element}\r\n`;
|
||||
});
|
||||
this.line = res.data.line;
|
||||
if (lines.length) {
|
||||
// 自动滚动到底部
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const textarea = document.getElementById("script-log-textarea");
|
||||
if (textarea) {
|
||||
textarea.scrollTop = textarea.scrollHeight;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.console {
|
||||
padding: 5px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 5px 5px;
|
||||
}
|
||||
</style>
|
142
web-vue/src/pages/node/node-layout/other/script-log.vue
Normal file
142
web-vue/src/pages/node/node-layout/other/script-log.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="node-full-content">
|
||||
<div ref="filter" class="filter">
|
||||
<a-input v-model="listQuery['%name%']" placeholder="名称" allowClear class="search-input-item" />
|
||||
<a-tooltip title="按住 Ctr 或者 Alt 键点击按钮快速回到第一页">
|
||||
<a-button type="primary" @click="loadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 数据表格 -->
|
||||
<a-table :data-source="list" :loading="loading" :columns="columns" @change="changePage" :pagination="pagination" bordered rowKey="id">
|
||||
<a-tooltip slot="scriptName" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip slot="modifyUser" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip slot="createTimeMillis" slot-scope="text, record" :title="`${parseTime(record.createTimeMillis)}`">
|
||||
<span>{{ parseTime(record.createTimeMillis) }}</span>
|
||||
</a-tooltip>
|
||||
<template slot="operation" slot-scope="text, record">
|
||||
<a-button type="primary" @click="viewLog(record)">查看日志</a-button>
|
||||
|
||||
<a-button type="danger" @click="handleDelete(record)">删除</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 日志 -->
|
||||
<a-modal :width="'80vw'" v-model="logVisible" title="执行日志" :footer="null" :maskClosable="false">
|
||||
<script-log-view v-if="logVisible" :temp="temp" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getScriptLogList, scriptDel } from "@/api/node-other";
|
||||
import ScriptLogView from "@/pages/node/node-layout/other/script-log-view";
|
||||
import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL, PAGE_DEFAULT_LIST_QUERY } from "@/utils/const";
|
||||
import { parseTime } from "@/utils/time";
|
||||
export default {
|
||||
components: {
|
||||
ScriptLogView,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
list: [],
|
||||
temp: {},
|
||||
logVisible: false,
|
||||
columns: [
|
||||
{ title: "名称", dataIndex: "scriptName", ellipsis: true, scopedSlots: { customRender: "scriptName" } },
|
||||
{ title: "执行时间", dataIndex: "createTimeMillis", ellipsis: true, scopedSlots: { customRender: "createTimeMillis" } },
|
||||
{ title: "执行人", dataIndex: "modifyUser", ellipsis: true, scopedSlots: { customRender: "modifyUser" } },
|
||||
{ title: "操作", dataIndex: "operation", scopedSlots: { customRender: "operation" }, width: 220 },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pagination() {
|
||||
return {
|
||||
total: this.listQuery.total,
|
||||
current: this.listQuery.page || 1,
|
||||
pageSize: this.listQuery.limit || PAGE_DEFAULT_LIMIT,
|
||||
pageSizeOptions: PAGE_DEFAULT_SIZW_OPTIONS,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => {
|
||||
return PAGE_DEFAULT_SHOW_TOTAL(total, this.listQuery);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData(pointerEvent) {
|
||||
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page;
|
||||
this.listQuery.nodeId = this.node.id;
|
||||
this.loading = true;
|
||||
getScriptLogList(this.listQuery).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.result;
|
||||
this.listQuery.total = res.data.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
parseTime(v) {
|
||||
return parseTime(v);
|
||||
},
|
||||
viewLog(record) {
|
||||
this.logVisible = true;
|
||||
this.temp = record;
|
||||
},
|
||||
handleDelete(record) {
|
||||
this.$confirm({
|
||||
title: "系统提示",
|
||||
content: "真的要删除执行记录么?",
|
||||
okText: "确认",
|
||||
cancelText: "取消",
|
||||
onOk: () => {
|
||||
// 组装参数
|
||||
const params = {
|
||||
nodeId: this.node.id,
|
||||
id: record.scriptId,
|
||||
executeId: record.id,
|
||||
};
|
||||
// 删除
|
||||
scriptDel(params).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.$notification.success({
|
||||
message: res.msg,
|
||||
});
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
// 分页、排序、筛选变化时触发
|
||||
changePage(pagination, filters, sorter) {
|
||||
this.listQuery.page = pagination.current;
|
||||
this.listQuery.limit = pagination.pageSize;
|
||||
if (sorter) {
|
||||
this.listQuery.order = sorter.order;
|
||||
this.listQuery.order_field = sorter.field;
|
||||
}
|
||||
this.loadData();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
@ -147,7 +147,7 @@ export default {
|
||||
this.socket.onerror = (err) => {
|
||||
console.error(err);
|
||||
this.$notification.error({
|
||||
message: "socket 错误,请检查是否开启 ws 代理,或者没有对应的权限",
|
||||
message: "web socket 错误,请检查是否开启 ws 代理,或者没有对应的权限",
|
||||
});
|
||||
};
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user