diff --git a/web/main/static/resources/scripts/task_modal.js b/web/main/static/resources/scripts/task_modal.js
new file mode 100644
index 0000000..9aa9097
--- /dev/null
+++ b/web/main/static/resources/scripts/task_modal.js
@@ -0,0 +1,659 @@
+ // 全局变量,用于保存当前选中的节点数据
+ let selectedNodeData = null;
+
+ /**
+ * 根据节点数据递归生成树形结构(返回
元素)
+ * 假设节点数据格式:
+ * {
+ * "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();
+ // 找到该节点下的 子节点列表
+ 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 =
+ "无节点数据
";
+ return;
+ }
+
+ // 创建一个 作为树的根容器
+ 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 = "加载节点树失败
";
+ }
+ }
+
+ 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 = `
+ |
+ |
+ |
+ |
+ `;
+ 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 = `
+ |
+ |
+ |
+ `;
+ 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 = "";
+ // 遍历数据行,生成
+ 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);
+});