脚本日志管理

This commit is contained in:
bwcx_jzy 2021-12-24 23:22:22 +08:00
parent da4b8396fb
commit abd246828e
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
16 changed files with 604 additions and 83 deletions

View File

@ -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, "删除成功");
}
}

View File

@ -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());

View File

@ -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),

View File

@ -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"),

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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);
}
});
}
}

View File

@ -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();

View File

@ -33,6 +33,10 @@
{
"id": "script",
"title": "脚本模板"
},
{
"id": "script-log",
"title": "脚本执行记录"
}
]
},

View File

@ -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 '脚本名称';

View File

@ -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

View File

@ -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 {

View File

@ -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>

View 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>

View 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>

View File

@ -147,7 +147,7 @@ export default {
this.socket.onerror = (err) => {
console.error(err);
this.$notification.error({
message: "socket 错误,请检查是否开启 ws 代理,或者没有对应的权限",
message: "web socket 错误,请检查是否开启 ws 代理,或者没有对应的权限",
});
};
},