|
|
@ -1,19 +1,50 @@ |
|
|
|
// 全局变量,用于保存当前选中的节点数据
|
|
|
|
let selectedNodeData = null; |
|
|
|
|
|
|
|
/** |
|
|
|
* 根据节点数据递归生成树形结构(返回 <li> 元素) |
|
|
|
* 假设节点数据格式: |
|
|
|
* { |
|
|
|
* "node_name":node.name, |
|
|
|
* "node_path":node.path, |
|
|
|
* "node_status":node.status, |
|
|
|
* "node_bwork":node.bwork, |
|
|
|
* "node_vultype":node.vul_type, |
|
|
|
* "node_vulgrade":node.vul_grade, |
|
|
|
* children: [ { ... }, { ... } ] |
|
|
|
* } |
|
|
|
*/ |
|
|
|
// 动态加载节点树数据
|
|
|
|
async function loadNodeTree(task_id) { |
|
|
|
// 清空选中状态
|
|
|
|
selectedNodeData = null; |
|
|
|
//刷新界面内容
|
|
|
|
update_select_node_data_show("-","-","-","-","-","-",false) |
|
|
|
|
|
|
|
try { |
|
|
|
const res = await fetch("/api/task/gettree", { |
|
|
|
method: "POST", |
|
|
|
headers: { "Content-Type": "application/json" }, |
|
|
|
body: JSON.stringify({ task_id }), //task_id:task_id
|
|
|
|
}); |
|
|
|
if (!res.ok) { |
|
|
|
const errorData = await res.json(); |
|
|
|
throw new Error(errorData.error || `HTTP错误 ${res.status}`); |
|
|
|
} |
|
|
|
const data = await res.json(); |
|
|
|
const treeData = data.tree; |
|
|
|
if (!treeData) { |
|
|
|
document.getElementById("treeContent").innerHTML = |
|
|
|
"<p>无节点数据</p>"; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 创建一个 <ul> 作为树的根容器
|
|
|
|
const ul = document.createElement("ul"); |
|
|
|
ul.className = "tree-root-ul"; |
|
|
|
ul.appendChild(generateTreeHTML(treeData)); //递归创建节点树
|
|
|
|
|
|
|
|
// 替换节点树容器的内容
|
|
|
|
const container = document.getElementById("treeContent"); |
|
|
|
container.innerHTML = ""; |
|
|
|
container.appendChild(ul); |
|
|
|
|
|
|
|
// 绑定节点点击事件
|
|
|
|
bindTreeNodeEvents(); |
|
|
|
} catch (error) { |
|
|
|
console.error("加载节点树失败:", error); |
|
|
|
document.getElementById("treeContent").innerHTML = "<p>加载节点树失败</p>"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function generateTreeHTML(nodeData) { |
|
|
|
const li = document.createElement("li"); |
|
|
|
const nodeSpan = document.createElement("span"); |
|
|
@ -25,7 +56,9 @@ |
|
|
|
nodeSpan.setAttribute("data-node_bwork", nodeData.node_bwork); |
|
|
|
nodeSpan.setAttribute("data-node_vultype", nodeData.node_vultype); |
|
|
|
nodeSpan.setAttribute("data-node_vulgrade", nodeData.node_vulgrade || ""); |
|
|
|
nodeSpan.setAttribute("data-node_vulinfo",nodeData.node_vulinfo); |
|
|
|
nodeSpan.setAttribute("data-node_workstatus",nodeData.node_workstatus); |
|
|
|
//显示界面
|
|
|
|
if(nodeData.node_workstatus ===0){ |
|
|
|
nodeSpan.classList.add("no-work"); |
|
|
|
}else { |
|
|
@ -41,9 +74,11 @@ |
|
|
|
nodeSpan.classList.add("vul-high"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//暂停状态样式
|
|
|
|
nodeSpan.classList.toggle('status-paused', nodeData.node_bwork === false); |
|
|
|
nodeSpan.classList.toggle('status-running', nodeData.node_bwork === true); |
|
|
|
|
|
|
|
// 创建容器用于存放切换图标与文本
|
|
|
|
const container = document.createElement("div"); |
|
|
|
container.className = "node-container"; |
|
|
@ -65,7 +100,7 @@ |
|
|
|
if (nodeData.children && nodeData.children.length > 0) { |
|
|
|
const ul = document.createElement("ul"); |
|
|
|
nodeData.children.forEach((child) => { |
|
|
|
ul.appendChild(generateTreeHTML(child)); |
|
|
|
ul.appendChild(generateTreeHTML(child)); //递归调用
|
|
|
|
}); |
|
|
|
li.appendChild(ul); |
|
|
|
} |
|
|
@ -79,8 +114,7 @@ |
|
|
|
// 阻止事件冒泡,避免点击时展开折叠影响
|
|
|
|
event.stopPropagation(); |
|
|
|
// 清除之前选中的节点样式
|
|
|
|
document |
|
|
|
.querySelectorAll(".tree-node.selected") |
|
|
|
document.querySelectorAll(".tree-node.selected") |
|
|
|
.forEach((node) => node.classList.remove("selected")); |
|
|
|
// 当前节点标记为选中
|
|
|
|
el.classList.add("selected"); |
|
|
@ -91,9 +125,9 @@ |
|
|
|
const nodebwork = el.getAttribute("data-node_bwork"); |
|
|
|
const vulType = el.getAttribute("data-node_vultype"); |
|
|
|
const vulLevel = el.getAttribute("data-node_vulgrade"); |
|
|
|
const vulInfo = el.getAttribute("data-node_vulinfo"); |
|
|
|
const workstatus = el.getAttribute("data-node_workstatus"); |
|
|
|
//selectedNodeData = { nodeName, status, vulType, vulLevel,nodepath,nodebwork };
|
|
|
|
// 示例中默认填充
|
|
|
|
//缓存一个选中节点的数据
|
|
|
|
selectedNodeData = { |
|
|
|
node_name: nodeName, |
|
|
|
node_path: nodepath, |
|
|
@ -101,11 +135,13 @@ |
|
|
|
node_bwork: nodebwork, |
|
|
|
vul_type: vulType, |
|
|
|
vul_grade: vulLevel || "-", |
|
|
|
vul_info:vulInfo, |
|
|
|
workstatus: workstatus |
|
|
|
}; |
|
|
|
//刷新界面内容
|
|
|
|
update_select_node_data_show(nodeName,status,vulType,vulLevel,workstatus,nodebwork) |
|
|
|
//刷新节点界面内容
|
|
|
|
update_select_node_data_show(nodeName,status,vulType,vulLevel,vulInfo,workstatus,nodebwork) |
|
|
|
}); |
|
|
|
|
|
|
|
// 双击事件:展开/收缩子节点区域
|
|
|
|
el.addEventListener("dblclick", (event) => { |
|
|
|
event.stopPropagation(); |
|
|
@ -127,102 +163,13 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// 动态加载节点树数据
|
|
|
|
async function loadNodeTree(task_id) { |
|
|
|
// 清空选中状态
|
|
|
|
selectedNodeData = null; |
|
|
|
//刷新界面内容
|
|
|
|
update_select_node_data_show("-","-","-","-","-",false) |
|
|
|
try { |
|
|
|
const res = await fetch("/api/task/gettree", { |
|
|
|
method: "POST", |
|
|
|
headers: { "Content-Type": "application/json" }, |
|
|
|
body: JSON.stringify({ task_id }), //task_id:task_id
|
|
|
|
}); |
|
|
|
if (!res.ok) { |
|
|
|
const errorData = await res.json(); |
|
|
|
throw new Error(errorData.error || `HTTP错误 ${res.status}`); |
|
|
|
} |
|
|
|
const data = await res.json(); |
|
|
|
const treeData = data.tree; |
|
|
|
if (!treeData) { |
|
|
|
document.getElementById("treeContent").innerHTML = |
|
|
|
"<p>无节点数据</p>"; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 创建一个 <ul> 作为树的根容器
|
|
|
|
const ul = document.createElement("ul"); |
|
|
|
ul.className = "tree-root-ul"; |
|
|
|
ul.appendChild(generateTreeHTML(treeData)); |
|
|
|
// 替换节点树容器的内容
|
|
|
|
const container = document.getElementById("treeContent"); |
|
|
|
container.innerHTML = ""; |
|
|
|
container.appendChild(ul); |
|
|
|
// 绑定节点点击事件
|
|
|
|
bindTreeNodeEvents(); |
|
|
|
} catch (error) { |
|
|
|
console.error("加载节点树失败:", error); |
|
|
|
document.getElementById("treeContent").innerHTML = "<p>加载节点树失败</p>"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function getWorkStatus_Str(workstatus){ |
|
|
|
strworkstatus = "" |
|
|
|
switch (workstatus){ |
|
|
|
case 0: |
|
|
|
strworkstatus = "无待执行任务"; |
|
|
|
break; |
|
|
|
case 1: |
|
|
|
strworkstatus = "待执行指令中"; |
|
|
|
break; |
|
|
|
case 2: |
|
|
|
strworkstatus = "指令执行中"; |
|
|
|
break; |
|
|
|
case 3: |
|
|
|
strworkstatus = "待提交llm中"; |
|
|
|
break; |
|
|
|
case 4: |
|
|
|
strworkstatus = "提交llm中"; |
|
|
|
break; |
|
|
|
default: |
|
|
|
strworkstatus = "-" |
|
|
|
} |
|
|
|
return strworkstatus |
|
|
|
} |
|
|
|
|
|
|
|
//根据web端过来的数据,更新节点的工作状态
|
|
|
|
function updateTreeNode(node_path, node_workstatus) { |
|
|
|
// 根据 node_path 查找对应节点(假设每个 .tree-node 上设置了 data-node_path 属性)
|
|
|
|
const nodeEl = document.querySelector(`.tree-node[data-node_path="${node_path}"]`); |
|
|
|
if (nodeEl) { |
|
|
|
// 更新 DOM 属性(属性值均为字符串)
|
|
|
|
nodeEl.setAttribute("data-node_workstatus", node_workstatus); |
|
|
|
//判断是否需要更新界面
|
|
|
|
if(selectedNodeData){ |
|
|
|
if(node_path === selectedNodeData.node_path){ //只有是当前选中节点才更新数据
|
|
|
|
selectedNodeData.workstatus = node_workstatus; |
|
|
|
strnew = getWorkStatus_Str(node_workstatus); |
|
|
|
work_status_el = document.getElementById("node_workstatus") |
|
|
|
work_status_el.textContent = strnew; |
|
|
|
work_status_el.classList.toggle('cmd-none',str_workStatus==="无待执行任务"); |
|
|
|
work_status_el.classList.toggle('cmd-pending',str_workStatus==="待执行指令中"); |
|
|
|
work_status_el.classList.toggle('cmd-running',str_workStatus==="指令执行中"); |
|
|
|
work_status_el.classList.toggle('cmd-waiting-llm',str_workStatus==="待提交llm中"); |
|
|
|
work_status_el.classList.toggle('cmd-submitting-llm',str_workStatus==="提交llm中"); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
console.warn(`未找到节点 ${node_path}`); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//刷新节点的数据显示
|
|
|
|
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,workStatus,nodebwork){ |
|
|
|
function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,vulInfo,workStatus,nodebwork){ |
|
|
|
document.getElementById("nodeName").textContent = nodeName; |
|
|
|
document.getElementById("testStatus").textContent = testStatus; |
|
|
|
document.getElementById("node_vulType").textContent = vulType; |
|
|
|
document.getElementById("node_vulLevel").textContent = vulLevel; |
|
|
|
document.getElementById("node_vulInfo").textContent = vulInfo; |
|
|
|
|
|
|
|
str_workStatus = getWorkStatus_Str(Number(workStatus)); |
|
|
|
work_status_el = document.getElementById("node_workstatus") |
|
|
@ -289,6 +236,58 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function getWorkStatus_Str(workstatus){ |
|
|
|
strworkstatus = "" |
|
|
|
switch (workstatus){ |
|
|
|
case 0: |
|
|
|
strworkstatus = "无待执行任务"; |
|
|
|
break; |
|
|
|
case 1: |
|
|
|
strworkstatus = "待执行指令中"; |
|
|
|
break; |
|
|
|
case 2: |
|
|
|
strworkstatus = "指令执行中"; |
|
|
|
break; |
|
|
|
case 3: |
|
|
|
strworkstatus = "待提交llm中"; |
|
|
|
break; |
|
|
|
case 4: |
|
|
|
strworkstatus = "提交llm中"; |
|
|
|
break; |
|
|
|
default: |
|
|
|
strworkstatus = "-" |
|
|
|
} |
|
|
|
return strworkstatus |
|
|
|
} |
|
|
|
|
|
|
|
//根据web端过来的数据,更新节点的工作状态
|
|
|
|
function updateTreeNode(node_path, node_workstatus) { |
|
|
|
// 根据 node_path 查找对应节点(假设每个 .tree-node 上设置了 data-node_path 属性)
|
|
|
|
const nodeEl = document.querySelector(`.tree-node[data-node_path="${node_path}"]`); |
|
|
|
if (nodeEl) { |
|
|
|
// 更新 DOM 属性(属性值均为字符串)
|
|
|
|
nodeEl.setAttribute("data-node_workstatus", node_workstatus); |
|
|
|
|
|
|
|
//判断是否需要更新界面
|
|
|
|
if(selectedNodeData){ |
|
|
|
if(node_path === selectedNodeData.node_path){ //只有是当前选中节点才更新数据
|
|
|
|
selectedNodeData.workstatus = node_workstatus; |
|
|
|
|
|
|
|
strnew = getWorkStatus_Str(node_workstatus); |
|
|
|
work_status_el = document.getElementById("node_workstatus") |
|
|
|
work_status_el.textContent = strnew; |
|
|
|
work_status_el.classList.toggle('cmd-none',str_workStatus==="无待执行任务"); |
|
|
|
work_status_el.classList.toggle('cmd-pending',str_workStatus==="待执行指令中"); |
|
|
|
work_status_el.classList.toggle('cmd-running',str_workStatus==="指令执行中"); |
|
|
|
work_status_el.classList.toggle('cmd-waiting-llm',str_workStatus==="待提交llm中"); |
|
|
|
work_status_el.classList.toggle('cmd-submitting-llm',str_workStatus==="提交llm中"); |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
console.warn(`未找到节点 ${node_path}`); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 刷新按钮事件绑定
|
|
|
|
document.getElementById("btnRefresh").addEventListener("click", () => { |
|
|
|
// 重新加载节点树数据
|
|
|
@ -327,8 +326,8 @@ |
|
|
|
//更新数据
|
|
|
|
const selectedEl = document.querySelector(".tree-node.selected"); |
|
|
|
if (selectedEl) { |
|
|
|
//selectedEl.setAttribute("data-node_bwork", newbwork);
|
|
|
|
selectedEl.dataset.node_bwok = newbwork; |
|
|
|
selectedEl.setAttribute("data-node_bwork", newbwork); |
|
|
|
//selectedEl.dataset.node_bwok = newbwork;
|
|
|
|
selectedNodeData.node_bwork = newbwork; |
|
|
|
//刷新界面
|
|
|
|
selectedEl.classList.toggle("status-running", Boolean(newbwork)); |
|
|
@ -389,8 +388,6 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------查看指令modal----------------------------
|
|
|
|
let doneInstrs = []; // 已执行指令的所有数据
|
|
|
|
let todoInstrs = []; // 待执行指令的所有数据
|
|
|
@ -692,41 +689,54 @@ function fallbackCopyTextToClipboard(text) { |
|
|
|
// 判断当前是哪个 Tab
|
|
|
|
const activeTab = document.querySelector("#instrTab button.nav-link.active"); |
|
|
|
if (activeTab.id === "doneInstrTab") { |
|
|
|
exportCurrentPage(doneInstrs, donePage, ["序号", "执行指令", "执行时间", "执行结果"]); |
|
|
|
exportCurrentPage(doneInstrs, ["序号", "执行指令", "执行时间", "执行结果"]); |
|
|
|
} else { |
|
|
|
exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]); |
|
|
|
exportCurrentPage(todoInstrs, ["序号", "待执行指令"]); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
function exportCurrentPage(dataArr, page, headerArr) { |
|
|
|
const startIndex = (page - 1) * pageSize; |
|
|
|
const endIndex = startIndex + pageSize; |
|
|
|
const pageData = dataArr.slice(startIndex, endIndex); |
|
|
|
// 处理 CSV 字段,确保特殊字符正确转义
|
|
|
|
function escapeCsvField(value) { |
|
|
|
if (value == null) return ""; |
|
|
|
const str = String(value).replace(/"/g, '""').replace(/\n/g, " "); |
|
|
|
return `"${str}"`; // 用引号包裹字段
|
|
|
|
} |
|
|
|
|
|
|
|
function exportCurrentPage(dataArr, headerArr) { |
|
|
|
// Check if in secure context
|
|
|
|
if (!window.isSecureContext) { |
|
|
|
console.error("Page is not in a secure context. Please load the page over HTTPS."); |
|
|
|
alert("无法导出文件:请使用 HTTPS 访问页面。"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 调试:检查 dataArr 内容
|
|
|
|
//console.log('Exporting dataArr:', dataArr);
|
|
|
|
// 如果 dataArr 为空,返回提示
|
|
|
|
if (!dataArr || dataArr.length === 0) { |
|
|
|
alert("没有数据可导出"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码
|
|
|
|
let csvContent = "\uFEFF" + headerArr.join(",") + "\n"; |
|
|
|
pageData.forEach((item, i) => { |
|
|
|
const rowIndex = startIndex + i + 1; |
|
|
|
dataArr.forEach((item, i) => { |
|
|
|
const rowIndex = i + 1; |
|
|
|
if (headerArr.length === 4) { |
|
|
|
// 已执行:序号,执行指令,执行时间,执行结果
|
|
|
|
csvContent += rowIndex + "," + |
|
|
|
(item.command || "") + "," + |
|
|
|
(item.execTime || "") + "," + |
|
|
|
(item.result || "") + "\n"; |
|
|
|
csvContent += |
|
|
|
[ |
|
|
|
rowIndex, |
|
|
|
escapeCsvField(item[0] || ""), // 调整为实际字段名
|
|
|
|
escapeCsvField(item[1] || ""), |
|
|
|
escapeCsvField(item[2] || ""), |
|
|
|
].join(",") + "\n"; |
|
|
|
} else { |
|
|
|
// 待执行:序号,待执行指令
|
|
|
|
csvContent += rowIndex + "," + (item.command || "") + "\n"; |
|
|
|
csvContent += |
|
|
|
[rowIndex, escapeCsvField(item.cmd || "")].join(",") + "\n"; |
|
|
|
} |
|
|
|
}); |
|
|
|
// 如果不足 pageSize 行,补足空行(根据列数进行适当补全)
|
|
|
|
for (let i = pageData.length; i < pageSize; i++) { |
|
|
|
// 根据 headerArr.length 来设置空行的格式
|
|
|
|
if (headerArr.length === 4) { |
|
|
|
csvContent += ",,,\n"; |
|
|
|
} else { |
|
|
|
csvContent += ",\n"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); |
|
|
|
const url = URL.createObjectURL(blob); |
|
|
@ -840,21 +850,14 @@ document.getElementById("submittedNext").addEventListener("click", function(e){ |
|
|
|
}); |
|
|
|
|
|
|
|
// 导出功能:导出当前页已提交数据到 CSV
|
|
|
|
document.getElementById("btnExportSubmitted").addEventListener("click", function(){ |
|
|
|
exportSubmittedCurrentPage(); |
|
|
|
}); |
|
|
|
// document.getElementById("btnExportSubmitted").addEventListener("click", function(){
|
|
|
|
// exportSubmittedCurrentPage();
|
|
|
|
// });
|
|
|
|
function exportSubmittedCurrentPage(){ |
|
|
|
const start = (submittedPage - 1) * pageSize; |
|
|
|
const end = start + pageSize; |
|
|
|
const pageData = submittedMsgs.slice(start, end); |
|
|
|
let csv = "\uFEFF" + "序号,角色,内容\n"; // 添加 BOM 防乱码
|
|
|
|
pageData.forEach((item, i) => { |
|
|
|
csv += `${start + i + 1},${item.role},${item.content}\n`; |
|
|
|
submittedMsgs.forEach((item, i) => { |
|
|
|
csv += `${i + 1},${escapeCsvField(item.role || "")},${escapeCsvField(item.content || "")}\n`; |
|
|
|
}); |
|
|
|
// 补空行
|
|
|
|
for(let i = pageData.length; i < pageSize; i++){ |
|
|
|
csv += ",,\n"; |
|
|
|
} |
|
|
|
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); |
|
|
|
const url = URL.createObjectURL(blob); |
|
|
|
const a = document.createElement("a"); |
|
|
|