// 全局变量,用于保存当前选中的节点数据 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: [ { ... }, { ... } ] * } */ function generateTreeHTML(nodeData) { const li = document.createElement("li"); const nodeSpan = document.createElement("span"); nodeSpan.className = "tree-node"; //设置data属性 nodeSpan.setAttribute("data-node_name", nodeData.node_name); nodeSpan.setAttribute("data-node_path", nodeData.node_path); nodeSpan.setAttribute("data-node_status", nodeData.node_status); 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_workstatus",nodeData.node_workstatus); if(nodeData.node_workstatus ===0){ nodeSpan.classList.add("no-work"); }else { nodeSpan.classList.remove("no-work"); } // 根据漏洞级别添加样式 if (nodeData.node_vulgrade) { nodeSpan.classList.remove("no-work"); if (nodeData.node_vulgrade === "低危") { nodeSpan.classList.add("vul-low"); } else if (nodeData.node_vulgrade === "中危") { nodeSpan.classList.add("vul-medium"); } else if (nodeData.node_vulgrade === "高危") { nodeSpan.classList.add("vul-high"); } } // 创建容器用于存放切换图标与文本 const container = document.createElement("div"); container.className = "node-container"; // 如果有子节点,则添加切换图标 if (nodeData.children && nodeData.children.length > 0) { const toggleIcon = document.createElement("span"); toggleIcon.className = "toggle-icon"; toggleIcon.textContent = "-"; // 默认展开时显示“-” container.appendChild(toggleIcon); } //节点文本 const textSpan = document.createElement("span"); textSpan.className = "node-text"; textSpan.textContent = nodeData.node_name; container.appendChild(textSpan); nodeSpan.appendChild(container); li.appendChild(nodeSpan); //如果存在子节点,递归生成子节点列表 if (nodeData.children && nodeData.children.length > 0) { const ul = document.createElement("ul"); nodeData.children.forEach((child) => { ul.appendChild(generateTreeHTML(child)); }); li.appendChild(ul); } return li; } // 绑定所有节点的点击事件 function bindTreeNodeEvents() { document.querySelectorAll(".tree-node").forEach((el) => { el.addEventListener("click", (event) => { // 阻止事件冒泡,避免点击时展开折叠影响 event.stopPropagation(); // 清除之前选中的节点样式 document .querySelectorAll(".tree-node.selected") .forEach((node) => node.classList.remove("selected")); // 当前节点标记为选中 el.classList.add("selected"); // 读取 data 属性更新右侧显示 const nodeName = el.getAttribute("data-node_name"); const status = el.getAttribute("data-node_status"); const nodepath = el.getAttribute("data-node_path"); const nodebwork = el.getAttribute("data-node_bwork"); const vulType = el.getAttribute("data-node_vultype"); const vulLevel = el.getAttribute("data-node_vulgrade"); const workstatus = el.getAttribute("data-node_workstatus"); //selectedNodeData = { nodeName, status, vulType, vulLevel,nodepath,nodebwork }; // 示例中默认填充 selectedNodeData = { node_name: nodeName, node_path: nodepath, status: status, node_bwork: nodebwork, vul_type: vulType, vul_grade: vulLevel || "-", workstatus: workstatus }; //刷新界面内容 update_select_node_data_show(nodeName,status,vulType,vulLevel,workstatus,nodebwork) }); // 双击事件:展开/收缩子节点区域 el.addEventListener("dblclick", (event) => { event.stopPropagation(); // 找到该节点下的 <ul> 子节点列表 const parentLi = el.parentElement; const childUl = parentLi.querySelector("ul"); if (childUl) { // 切换 collapsed 类,控制 display childUl.classList.toggle("collapsed"); // 更新切换图标 const toggleIcon = el.querySelector(".toggle-icon"); if (toggleIcon) { toggleIcon.textContent = childUl.classList.contains("collapsed") ? "+" : "-"; } } }); }); } // 动态加载节点树数据 async function loadNodeTree(task_id) { // 清空选中状态 selectedNodeData = null; //刷新界面内容 update_select_node_data_show("-","-","-","-","-",false) try { const res = await fetch("/api/task/gethistree", { 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); document.getElementById("node_workstatus").textContent = strnew; } } } else { console.warn(`未找到节点 ${node_path}`); } } //刷新节点的数据显示 function update_select_node_data_show(nodeName,testStatus,vulType,vulLevel,workStatus,nodebwork){ document.getElementById("nodeName").textContent = nodeName; document.getElementById("testStatus").textContent = testStatus; document.getElementById("node_vulType").textContent = vulType; document.getElementById("node_vulLevel").textContent = vulLevel; str_workStatus = getWorkStatus_Str(Number(workStatus)); document.getElementById("node_workstatus").textContent = str_workStatus; if(nodebwork==="true"){ document.getElementById("node_bwork").textContent = "执行中"; }else { document.getElementById("node_bwork").textContent = "暂停中"; } setNodeBtnStatus(); } //节点按钮的状态控制 function setNodeBtnStatus(){ const btn_VI = document.getElementById("btnViewInstr"); if(!selectedNodeData){ //没有选择node,按钮全部置不可用 btn_VI.disabled = true; btn_VI.classList.add("disabled-btn"); } else{ btn_VI.disabled = false; btn_VI.classList.remove("disabled-btn"); } } // // 刷新按钮事件绑定 // document.getElementById("btnRefresh").addEventListener("click", () => { // // 重新加载节点树数据 // loadNodeTree(cur_task_id); // }); // 按钮事件:当未选中节点时提示 function checkSelectedNode() { if (!selectedNodeData) { alert("请先选择节点"); return false; } return true; } //----------------------查看节点--指令modal---------------------------- let doneInstrs = []; // 已执行指令的所有数据 let todoInstrs = []; // 待执行指令的所有数据 let donePage = 1; // 已执行指令当前页 let todoPage = 1; // 待执行指令当前页 document.getElementById("btnViewInstr").addEventListener("click", () => { if (!checkSelectedNode()) return; openInstrModal() }); // 打开对话框函数 function openInstrModal() { const instrCanvas = new bootstrap.Offcanvas(document.getElementById('instrCanvas')); instrCanvas.show(); // const modalEl = document.getElementById("instrModal"); // // 假设用 Bootstrap 5 的 Modal 组件 // const instrModal = new bootstrap.Modal(modalEl, {keyboard: false}); // 显示对话框 //instrModal.show(); // 在打开 modal 时,先更新提示内容,将 loadingMsg 显示“请稍后,数据获取中…” const loadingMsg = document.getElementById("loadingMsg"); if (loadingMsg) { loadingMsg.textContent = "请稍后,数据获取中..."; } // 加载指令数据 loadInstrData(); } // 调用后端接口,获取指令数据 async function loadInstrData() { task_id = cur_task_id; node_path = selectedNodeData.node_path; try { const res = await fetch("/api/task/hisnodegetinstr", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({task_id,node_path}), }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.error || `HTTP错误 ${res.status}`); } const data = await res.json(); // 数据获取成功后,清除加载提示 const loadingMsg = document.getElementById("loadingMsg"); if (loadingMsg) { loadingMsg.style.display = "none"; // 或者清空其 innerHTML } doneInstrs = data.doneInstrs || []; //todoInstrs = data.todoInstrs || []; donePage = 1; todoPage = 1; renderDoneInstrTable(donePage); //renderTodoInstrTable(todoPage); } catch (error) { console.error("加载指令数据异常:", error); } } // 渲染已执行指令表格 function renderDoneInstrTable(page) { const tbody = document.getElementById("doneInstrTbody"); // 计算起始索引 const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const pageData = doneInstrs.slice(startIndex, endIndex); //select instruction,start_time,result from task_result where task_id=%s and node_path=%s; tbody.innerHTML = ""; // 插入行 pageData.forEach((item, i) => { const tr = document.createElement("tr"); // 第一列:序号 const tdIndex = document.createElement("td"); tdIndex.textContent = startIndex + i + 1; tr.appendChild(tdIndex); // 第二列:指令内容 const tdInstr = document.createElement("td"); tdInstr.textContent = item[0]; tr.appendChild(tdInstr); // 第三列:开始时间(如果没有则显示空字符串) const tdStartTime = document.createElement("td"); tdStartTime.textContent = item[1] || ""; tr.appendChild(tdStartTime); // 第四列:执行结果 const tdResult = document.createElement("td"); tdResult.textContent = item[2] || ""; tr.appendChild(tdResult); tbody.appendChild(tr); }); // 若不足 10 行,补空行 for (let i = pageData.length; i < pageSize; i++) { const tr = document.createElement("tr"); tr.innerHTML = ` <td> </td> <td> </td> <td> </td> <td> </td> `; tbody.appendChild(tr); } } // 渲染待执行指令表格 function renderTodoInstrTable(page) { const tbody = document.getElementById("todoInstrTbody"); const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const pageData = todoInstrs.slice(startIndex, endIndex); tbody.innerHTML = ""; pageData.forEach((item, i) => { const tr = document.createElement("tr"); const idx = startIndex + i + 1; // 第一列:序号 const tdIndex = document.createElement("td"); tdIndex.textContent = idx; tr.appendChild(tdIndex); // 第二列:指令文本内容(直接使用 textContent) const tdItem = document.createElement("td"); tdItem.textContent = item; // 使用 textContent 避免 HTML 解析 tr.appendChild(tdItem); // 第三列:复制和删除按钮 const tdAction = document.createElement("td"); // const btn_cp = document.createElement("button"); // btn_cp.className = "btn btn-primary btn-sm"; // btn_cp.textContent = "复制"; // btn_cp.style.marginRight = "2px"; // 设置间隔 // btn_cp.onclick = () => confirmCopyTodoInstr(idx - 1); // tdAction.appendChild(btn_cp); const btn = document.createElement("button"); btn.className = "btn btn-danger btn-sm"; btn.textContent = "删除"; btn.onclick = () => confirmDeleteTodoInstr(idx - 1); tdAction.appendChild(btn); tr.appendChild(tdAction); tbody.appendChild(tr); }); // 补空行 for (let i = pageData.length; i < pageSize; i++) { const tr = document.createElement("tr"); tr.innerHTML = ` <td> </td> <td> </td> <td> </td> `; tbody.appendChild(tr); } } // 分页事件 document.getElementById("doneInstrPrev").addEventListener("click", (e) => { e.preventDefault(); if (donePage > 1) { donePage--; renderDoneInstrTable(donePage); } }); document.getElementById("doneInstrNext").addEventListener("click", (e) => { e.preventDefault(); const maxPage = Math.ceil(doneInstrs.length / pageSize); if (donePage < maxPage) { donePage++; renderDoneInstrTable(donePage); } }); document.getElementById("todoInstrPrev").addEventListener("click", (e) => { e.preventDefault(); if (todoPage > 1) { todoPage--; renderTodoInstrTable(todoPage); } }); document.getElementById("todoInstrNext").addEventListener("click", (e) => { e.preventDefault(); const maxPage = Math.ceil(todoInstrs.length / pageSize); if (todoPage < maxPage) { todoPage++; renderTodoInstrTable(todoPage); } }); // 导出当前页数据 document.getElementById("btnExport").addEventListener("click", () => { // 判断当前是哪个 Tab const activeTab = document.querySelector("#instrTab button.nav-link.active"); if (activeTab.id === "doneInstrTab") { exportCurrentPage(doneInstrs, donePage, ["序号", "执行指令", "执行时间", "执行结果"]); } else { exportCurrentPage(todoInstrs, todoPage, ["序号", "待执行指令"]); } }); function exportCurrentPage(dataArr, page, headerArr) { const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const pageData = dataArr.slice(startIndex, endIndex); // 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码 let csvContent = "\uFEFF" + headerArr.join(",") + "\n"; pageData.forEach((item, i) => { const rowIndex = startIndex + i + 1; if (headerArr.length === 4) { // 已执行:序号,执行指令,执行时间,执行结果 csvContent += rowIndex + "," + (item.command || "") + "," + (item.execTime || "") + "," + (item.result || "") + "\n"; } else { // 待执行:序号,待执行指令 csvContent += rowIndex + "," + (item.command || "") + "\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); const link = document.createElement("a"); link.href = url; link.download = "指令导出.csv"; link.click(); URL.revokeObjectURL(url); } //------------------测试数据和漏洞数据tab------------------- // 复用:根据返回的数据数组渲染表格 tbody,保证固定 10 行 function renderTableRows(tbody, rowsData) { tbody.innerHTML = ""; // 遍历数据行,生成 <tr> rowsData.forEach((row, index) => { const tr = document.createElement("tr"); // 这里假设 row 为对象,包含各个字段;下标从1开始显示序号 for (const cellData of Object.values(row)) { const td = document.createElement("td"); td.textContent = cellData; tr.appendChild(td); } tbody.appendChild(tr); }); // 补足空行 const rowCount = rowsData.length; for (let i = rowCount; i < pageSize; i++) { const tr = document.createElement("tr"); for (let j = 0; j < tbody.parentElement.querySelectorAll("th").length; j++) { const td = document.createElement("td"); td.innerHTML = " "; tr.appendChild(td); } tbody.appendChild(tr); } } //--------------------------测试指令------------------------------- let allInstrs = []; let currentInstrPage = 1; function renderInstrPage(page) { currentInstrPage = page; const start = (page - 1) * pageSize; const end = start + pageSize; const pageData = allInstrs.slice(start, end); const tbody = document.querySelector("#instrTable tbody"); renderTableRows(tbody, pageData); // 更新分页按钮 document.getElementById("instrPrev").dataset.page = page > 1 ? page - 1 : 1; document.getElementById("instrNext").dataset.page = (end < allInstrs.length) ? page + 1 : page; } // 查询测试指令 async function searchInstructions(page = 1) { if(cur_task_id === 0){ return; } const nodeName = document.getElementById("instrNodeName").value.trim(); try { const res = await fetch("/api/task/getinstr", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ cur_task_id, nodeName }), }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.error || `HTTP错误 ${res.status}`); } const data = await res.json(); allInstrs = data.instrs; renderInstrPage(1); //显示第一页数据 } catch (error) { console.error("获取测试指令失败:", error); } } //导出测试指令数据 async function ExportInstructions(){ alert("导出指令功能实现中。。。") } // 绑定测试指令查询按钮事件 document.getElementById("instrSearchBtn").addEventListener("click", () => { searchInstructions(); }); // 绑定测试指令分页点击事件 document.getElementById("instrPrev").addEventListener("click", (e) => { const page = parseInt(e.target.dataset.page, 10); renderInstrPage(page); }); document.getElementById("instrNext").addEventListener("click", (e) => { const page = parseInt(e.target.dataset.page, 10); renderInstrPage(page);; }); //------------------漏洞数据--------------------------------- let allVuls = []; let currentVulPage = 1; function renderVulPage(page) { currentVulPage = page; const start = (page - 1) * pageSize; const end = start + pageSize; const pageData = allVuls.slice(start, end); const tbody = document.querySelector("#vulTable tbody"); renderTableRows(tbody, pageData); // 更新分页按钮 document.getElementById("vulPrev").dataset.page = page > 1 ? page - 1 : 1; document.getElementById("vulNext").dataset.page = (end < allVuls.length) ? page + 1 : page; } // 查询漏洞数据 async function searchVulnerabilities(page = 1) { if(cur_task_id === 0){return;} const nodeName = document.getElementById("vulNodeName").value.trim(); const vulType = document.getElementById("vulType").value.trim(); const vulLevel = document.getElementById("vulLevel").value; try { const res = await fetch("/api/task/getvul", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ cur_task_id, nodeName, vulType, vulLevel }), }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.error || `HTTP错误 ${res.status}`); } const data = await res.json(); allVuls = data.vuls; renderVulPage(1) } catch (error) { console.error("获取漏洞数据失败:", error); } } //导出漏洞数据 async function ExportVuls(){ alert("导出漏洞功能实现中。。。") } // 绑定漏洞数据查询按钮事件 document.getElementById("vulSearchBtn").addEventListener("click", () => { searchVulnerabilities(); }); // 绑定漏洞数据分页点击事件 document.getElementById("vulPrev").addEventListener("click", (e) => { const page = parseInt(e.target.dataset.page, 10); renderVulPage(page); }); document.getElementById("vulNext").addEventListener("click", (e) => { const page = parseInt(e.target.dataset.page, 10); renderVulPage(page); });