You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

659 lines
22 KiB

// 全局变量,用于保存当前选中的节点数据
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>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</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>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</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 = "&nbsp;";
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);
});