{% extends 'base.html' %} {% block title %}ZFSAFE{% endblock %} <!-- 在此处可添加样式文件 --> {% block style_link %} <link href="{{ url_for('main.static', filename='css/node_tree.css') }}" rel="stylesheet"> {% endblock %} <!-- 页面样式块 --> {% block style %} /* 查询条件区域:使用 row 分布,输入框占满所在列 */ .search-section .form-control, .search-section .form-select { width: 100%; } /* 查询条件区域,每个条件统一高度且左右间隔均等 */ .search-section .col { padding: 0 5px; } /* 表格样式:统一垂直居中 */ .table thead th, .table tbody td { vertical-align: middle; text-align: center; } /* 分页区域右对齐 */ .pagination-section { text-align: right; padding-right: 15px; } /* 固定行高,比如 45px,每页 10 行 */ .fixed-row-height { height: 45px; overflow: hidden; } .tab-wrapper { max-height: calc(100vh - 60px - 56px - 66px - 14px); overflow: hidden; /* 防止溢出 */ } .tab-content { max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px); overflow: hidden; /* 防止溢出 */ } .tab-pane { max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px); overflow-x: hidden; overflow-y: auto; .row { max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px); overflow: hidden; /* 防止溢出 */ } .his-node-tree-area { width: 100%; max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px); border: 1px solid #ddd; /* overflow: hidden; 超出时出现滚动条 */ overflow-x: auto; overflow-y: hidden; background-color: #f8f9fa; text-align: center; /* 内部 inline-block 居中 */ position: relative; } /* 树节点内容区域,不包含刷新按钮 */ .his-tree-content { max-height: calc(100vh - 60px - 56px - 66px - 14px - 42px - 35px); overflow-x: auto; overflow-y: auto; padding-top: 5px; /* 留出顶部刷新按钮位置 */ /* 关键一行:至少宽度撑满其内部内容 */ min-width: max-content; } /* 这里设置页码按钮样式(可根据需要调整) */ .pagination { margin: 0; } .disabled-btn { /* 禁用状态样式 */ background-color: #cccccc; /* 灰色背景 */ color: #666666; /* 文字颜色变浅 */ cursor: not-allowed; /* 鼠标显示禁用图标 */ opacity: 0.7; /* 可选:降低透明度 */ /* 禁用点击事件(通过 disabled 属性已实现,此样式仅增强视觉效果) */ pointer-events: none; /* 可选:彻底阻止鼠标事件 */ } .offcanvas-backdrop.show { z-index: 1055; } /* 再把 offcanvas 本身提到更高,超过 modal(modal 是 1055) */ .offcanvas.show { z-index: 1060; } /* 让所有右侧 offcanvas-end 都变成 60% 宽 */ .offcanvas.offcanvas-end { width: 60% !important; max-width: none; /* 取消默认 max-width */ } {% endblock %} <!-- 页面内容块 --> {% block content %} <div class="container"> <!-- 查询条件区域 --> <div class="search-section mb-3"> <form class="row g-3 align-items-center"> <!-- 每个输入框直接使用 placeholder 显示标题,水平分布 --> <div class="col-3"> <input type="text" class="form-control" id="testTarget" name="target" placeholder="检测目标"> </div> <div class="col-2"> <select class="form-select" id="riskLevel" name="risk_level"> <option value="">风险级别</option> <option value="0">0</option> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8">8</option> <option value="9">9</option> </select> </div> <div class="col-2"> <select class="form-select" id="useModel" name="model"> <option value="">使用模型</option> <option value="1">DeepSeek</option> <option value="2">GPT-O3</option> <option value="4">Qwen3</option> </select> </div> <div class="col-2" > <input type="date" id="startTime" class="form-control" name="start_time" placeholder="开始时间"> </div> <div class="col-2"> <input type="date" id="endTime" class="form-control" name="end_time" placeholder="结束时间"> </div> <div class="col-auto"> <button type="button" class="btn btn-primary" id="btnQuery">查询</button> </div> </form> </div> <!-- 表格区域 --> <div class="table-section mb-3"> <table class="table table-bordered table-hover" id="histasksTable" style="width: 100%; table-layout: fixed;"> <thead class="table-light"> <tr> <th style="width: 5%;">ID</th> <th style="width: 20%;">检测目标</th> <th style="width: 15%;">开始时间</th> <th style="width: 15%;">结束时间</th> <th style="width: 10%;">风险等级</th> <th style="width: 15%;">使用模型</th> <th style="width: 20%;">操作</th> </tr> </thead> <tbody id="histasksTbody"> <!-- 数据由JS动态填充,固定10行一页 --> </tbody> </table> </div> <!-- 分页控件区域 --> <div class="pagination-section mb-3"> <nav> <ul class="pagination pagination-sm justify-content-end" id="histasksPagination"> <li class="page-item"> <a class="page-link" href="#" id="prevPage">上一页</a> </li> <!-- 页码动态生成 --> <li class="page-item"> <a class="page-link" href="#" id="nextPage">下一页</a> </li> </ul> </nav> </div> </div> <!-- 模态框:显示 task_manager.html 中的中间 tab 页内容 --> <!-- 这里只显示节点树、测试指令、漏洞数据三个 tab 页 --> <div class="modal fade" id="viewModal" tabindex="-1" aria-labelledby="viewModalLabel" aria-hidden="true"> <div class="modal-dialog modal-xl"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="viewModalLabel">任务详情</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭"></button> </div> <div class="modal-body p-0"> <!-- 这里仅嵌入中间 tab 页部分 --> <div class="tab-wrapper"> <ul class="nav nav-tabs" id="myTab" role="tablist"> <li class="nav-item" role="presentation"> <button class="nav-link active" id="nodeTreeTab" data-bs-toggle="tab" data-bs-target="#nodeTree" type="button" role="tab" aria-controls="nodeTree" aria-selected="true"> 节点树 </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link" id="testInstructionsTab" data-bs-toggle="tab" data-bs-target="#testInstructions" type="button" role="tab" aria-controls="testInstructions" aria-selected="false"> 测试指令 </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link" id="vulnerabilitiesTab" data-bs-toggle="tab" data-bs-target="#vulnerabilities" type="button" role="tab" aria-controls="vulnerabilities" aria-selected="false"> 漏洞数据 </button> </li> </ul> <div class="tab-content" id="myTabContent"> <!-- 节点树 --> <div class="tab-pane fade show active p-3" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab"> <div class="row"> <!-- 左侧:节点树区域 --> <div class="col-8 h-100"> <div class="his-node-tree-area" id="nodeTreeContainer"> <!-- 节点树内容 --> <div id="treeContent" class="his-tree-content"> <p id="treeLoadingMsg" style="text-align:center;">加载中...</p> </div> </div> </div> <!-- 右侧:节点信息与操作 --> <div class="col-4 h-100"> <div class="node-info-area mb-2" style="padding: 10px;"> <!-- <h5>节点信息</h5>--> <p><strong>节点名称:</strong> <span id="nodeName">-</span></p> <p><strong>测试状态:</strong> <span id="testStatus">-</span></p> <p><strong>漏洞类型:</strong> <span id="node_vulType">-</span></p> <p><strong>漏洞级别:</strong> <span id="node_vulLevel">-</span></p> <p><strong>漏洞说明:</strong> <span id="node_vulInfo">-</span></p> <p><strong>工作状态:</strong> <span id="node_bwork">-</span></p> <p><strong>执行状态:</strong> <span id="node_workstatus">-</span></p> </div> <div class="node-actions" style="padding: 0 10px 10px;"> <div class="row mb-2"> <div class="col-12"> <button class="btn btn-primary w-100" id="btnViewInstr">查看指令</button> </div> </div> </div> </div> </div> </div> <!-- 测试指令 --> <div class="tab-pane fade p-3" id="testInstructions" role="tabpanel" aria-labelledby="testInstructionsTab"> <div class="row search-area mb-2"> <div class="col-4"> <input type="text" class="form-control" id="instrNodeName" placeholder="节点名称"> </div> <div class="col-2"> <button class="btn btn-primary" id="instrSearchBtn">查询</button> <button class="btn btn-primary" id="instrExportBtn">导出</button> </div> </div> <table class="table table-bordered table-hover" id="instrTable" style="width: 100%; table-layout: fixed;"> <colgroup> <col style="width: 5%;"> <col style="width: 15%;"> <col style="width: 5%;"> <col style="width: 30%;" class="wrap-cell"> <col style="width: auto;"> </colgroup> <thead> <tr> <th>序号</th> <th>节点路径</th> <th>指序</th> <th>执行指令</th> <th>执行结果</th> </tr> </thead> <tbody> <!-- 默认显示10行 --> </tbody> </table> <!-- 分页控件 --> <nav> <ul class="pagination" id="instrPagination"> <li class="page-item"> <a class="page-link" href="#" id="instrPrev">上一页</a> </li> <li class="page-item"> <a class="page-link" href="#" id="instrNext">下一页</a> </li> </ul> </nav> </div> <!-- 漏洞数据 --> <div class="tab-pane fade p-3" id="vulnerabilities" role="tabpanel" aria-labelledby="vulnerabilitiesTab"> <div class="row search-area mb-2"> <div class="col-3"> <input type="text" class="form-control" id="vulNodeName" placeholder="节点名称"> </div> <div class="col-3"> <input type="text" class="form-control" id="vulType" placeholder="漏洞类型"> </div> <div class="col-3"> <select class="form-select" id="vulLevel"> <option value="">漏洞级别</option> <option value="低危">低危</option> <option value="中危">中危</option> <option value="高危">高危</option> </select> </div> <div class="col-2"> <button class="btn btn-primary" id="vulSearchBtn">查询</button> <button class="btn btn-primary" id="vulExportBtn">导出</button> </div> </div> <table class="table table-bordered table-hover" id="vulTable"> <thead> <tr> <th class="seq-col">序号</th> <th>节点路径</th> <th>漏洞类型</th> <th>漏洞级别</th> <th>漏洞说明</th> </tr> </thead> <tbody> <!-- 默认显示10行 --> </tbody> </table> <!-- 分页控件 --> <nav> <ul class="pagination" id="vulPagination"> <li class="page-item"> <a class="page-link" href="#" id="vulPrev">上一页</a> </li> <li class="page-item"> <a class="page-link" href="#" id="vulNext">下一页</a> </li> </ul> </nav> </div> </div> </div> </div> <!-- 模态框 footer --> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> </div> </div> </div> </div> <!-- 查看节点执行指令offcanvas --modal 改---> <div class="offcanvas offcanvas-end" tabindex="-1" id="instrCanvas" aria-labelledby="instrCanvasLabel"> <div class="offcanvas-header"> <!-- 返回按钮 --> <!-- <button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="offcanvas">--> <!-- ←--> <!-- </button>--> <h5 class="offcanvas-title" id="instrOffcanvasLabel">测试指令</h5> <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body"> <!-- 返回按钮 --> <div class="mb-3"> <button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="offcanvas"> ← 返回 </button> </div> <!-- 新增一个提示容器 --> <!-- <div id="loadingMsg" style="text-align: center; padding: 10px;">请稍后,数据获取中...</div>--> <div id="loadingMsg" class="text-center mb-3">请稍后,数据获取中...</div> <!-- 页签(已执行、待执行) --> <ul class="nav nav-tabs" id="instrTab" role="tablist"> <li class="nav-item" role="presentation"> <button class="nav-link active" id="doneInstrTab" data-bs-toggle="tab" data-bs-target="#doneInstr" type="button" role="tab" aria-controls="doneInstr" aria-selected="true" > 已执行 </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link" id="todoInstrTab" data-bs-toggle="tab" data-bs-target="#todoInstr" type="button" role="tab" aria-controls="todoInstr" aria-selected="false" > 待执行 </button> </li> </ul> <div class="tab-content pt-3" id="instrTabContent"> <!-- 已执行指令表格 --> <div class="tab-pane fade show active" id="doneInstr" role="tabpanel" aria-labelledby="doneInstrTab" > <table class="table table-bordered table-hover"> <thead> <tr> <th style="width: 50px;">序号</th> <th>执行指令</th> <th>执行时间</th> <th>执行结果</th> </tr> </thead> <tbody id="doneInstrTbody"> <!-- 动态生成,固定 10 行 --> </tbody> </table> <!-- 分页控件 --> <nav> <ul class="pagination justify-content-end" id="doneInstrPagination"> <li class="page-item"> <a class="page-link" href="#" id="doneInstrPrev">上一页</a> </li> <li class="page-item"> <a class="page-link" href="#" id="doneInstrNext">下一页</a> </li> </ul> </nav> </div> <!-- 待执行指令表格 --> <div class="tab-pane fade" id="todoInstr" role="tabpanel" aria-labelledby="todoInstrTab" > <table class="table table-bordered table-hover"> <thead> <tr> <th style="width: 50px;">序号</th> <th>待执行指令</th> <th style="width: 80px;">操作</th> </tr> </thead> <tbody id="todoInstrTbody"> <!-- 动态生成,固定 10 行 --> </tbody> </table> <!-- 分页控件 --> <nav> <ul class="pagination justify-content-end" id="todoInstrPagination"> <li class="page-item"> <a class="page-link" href="#" id="todoInstrPrev">上一页</a> </li> <li class="page-item"> <a class="page-link" href="#" id="todoInstrNext">下一页</a> </li> </ul> </nav> </div> </div> <!-- 操作按钮 --> <div class="mt-4 d-flex justify-content-end"> <button type="button" class="btn btn-primary" id="btnExport">导出</button> </div> </div> </div> {% endblock %} <!-- 页面脚本块 --> {% block script %} <script src="{{ url_for('main.static', filename='scripts/his_task_modal.js') }}"></script> <script> // 全局变量 let cur_task_id = 0; let allHistasks = []; let currentPage = 1; const pageSize = 10; // 分页渲染函数 function renderHistasksTable(page) { currentPage = page; const tbody = document.getElementById("histasksTbody"); const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const pageData = allHistasks.slice(startIndex, endIndex); //select ID,task_target,safe_rank,llm_type,start_time,end_time from task tbody.innerHTML = ""; pageData.forEach((task, i) => { const tr = document.createElement("tr"); // 每个单元格创建时使用 textContent,确保固定行高,如有需要也可增加 class "fixed-row-height" const tdId = document.createElement("td"); tdId.textContent = task[0]; tr.appendChild(tdId); const tdTarget = document.createElement("td"); tdTarget.textContent = task[1]; tr.appendChild(tdTarget); const tdStart = document.createElement("td"); tdStart.textContent = task[4] || ""; tr.appendChild(tdStart); const tdEnd = document.createElement("td"); tdEnd.textContent = task[5] || ""; tr.appendChild(tdEnd); const tdRisk = document.createElement("td"); tdRisk.textContent = (task[2] === 0) ? "安全" : "存在风险"; tr.appendChild(tdRisk); const tdModel = document.createElement("td"); model_test = "" if(task[3]===1){ model_test="DeepSeek"; } else if(task[3]===2){ model_test="GPT-O3"; } else if(task[3]===4){ model_test="Qwen3"; } else{ model_test="其他模型"; } tdModel.textContent = model_test; tr.appendChild(tdModel); const tdAction = document.createElement("td"); //报告按钮 const btnReport = document.createElement("button"); btnReport.className = "btn btn-outline-info btn-sm ms-2"; btnReport.textContent = "报告"; btnReport.onclick = () => createReport(task[0]); tdAction.appendChild(btnReport); // 查看按钮(点击后弹出 modal) const btnView = document.createElement("button"); btnView.className = "btn btn-outline-info btn-sm ms-2"; btnView.textContent = "查看"; btnView.onclick = () => openViewModal(task[0]); tdAction.appendChild(btnView); // 删除按钮 const btnDel = document.createElement("button"); btnDel.className = "btn btn-outline-danger btn-sm ms-2"; btnDel.textContent = "删除"; btnDel.onclick = () => confirmDeleteTask(task[0]); tdAction.appendChild(btnDel); tr.appendChild(tdAction); tbody.appendChild(tr); }); // 补空行 for (let i = pageData.length; i < pageSize; i++) { const tr = document.createElement("tr"); for (let j = 0; j < 7; j++) { const td = document.createElement("td"); td.textContent = "\u00A0"; tr.appendChild(td); } tbody.appendChild(tr); } updatePagination(); } function createReport(task_id){ alert("导出报告的功能实现中--"+task_id) } // 更新分页按钮 function updatePagination() { const totalPages = Math.ceil(allHistasks.length / pageSize); document.getElementById("prevPage").dataset.page = currentPage > 1 ? currentPage - 1 : 1; document.getElementById("nextPage").dataset.page = currentPage < totalPages ? currentPage + 1 : totalPages; } // 分页按钮点击事件 document.getElementById("prevPage").addEventListener("click", function(e) { e.preventDefault(); const page = parseInt(this.dataset.page, 10); renderHistasksTable(page); }); document.getElementById("nextPage").addEventListener("click", function(e) { e.preventDefault(); const page = parseInt(this.dataset.page, 10); renderHistasksTable(page); }); // 查询按钮事件,调用 /api/task/histasks 接口获取数据(示例) document.getElementById("btnQuery").addEventListener("click", async function() { // 此处可拼接获取表单数据条件,示例直接调用接口 /* target_name = data.get("target_name") safe_rank = data.get("safe_rank") llm_type = data.get("llm_type") start_time= data.get("start_time") end_time= data.get("end_time") * */ const target_name = document.getElementById("testTarget").value.trim(); const safe_rank = document.getElementById("riskLevel").value; const llm_type = document.getElementById("useModel").value; const start_time = document.getElementById("startTime").value; const end_time = document.getElementById("endTime").value; try { const res = await fetch("/api/task/histasks", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({target_name,safe_rank,llm_type,start_time,end_time}) }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.error || `HTTP错误 ${res.status}`); } const data = await res.json(); allHistasks = data.his_tasks || []; renderHistasksTable(1); } catch (error) { console.error("查询任务记录出错:", error); alert("查询失败!"); } }); // “查看详情”按钮事件(统一使用模态框显示 task_manager.html) async function openViewModal(task_id) { cur_task_id = task_id; const viewModal = new bootstrap.Modal(document.getElementById("viewModal"), { keyboard: false }); viewModal.show(); //查询节点树数据 his_loadNodeTree(task_id); //查询指令数据 searchInstructions(1); //查询漏洞数据 searchVulnerabilities(1); } // 删除任务的示例函数 async function confirmDeleteTask(task_id) { if (confirm("确认删除任务 " + task_id + " 吗?")) { // 发送删除请求... try { const res = await fetch("/api/task/deltask", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({task_id}), }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.error || `HTTP错误 ${res.status}`); } const data = await res.json(); bsuccess = data.bsuccess; if(bsuccess){ // 1. 从前端缓存里删除这条任务 allHistasks = allHistasks.filter(t => t[0] !== task_id); // 2. 重新渲染当前页 // 注意:如果删除后当前页已经没有任何数据了,可以让 currentPage-- const totalPages = Math.ceil(allHistasks.length / pageSize) || 1; if (currentPage > totalPages) { currentPage = totalPages; } renderHistasksTable(currentPage); // (可选)如果你想做局部删除,而不重画整表,也可以直接: // btnEl.closest("tr").remove(); alert("删除成功") } else{ alert("删除失败:"+data.error) return false; } } catch (error) { console.error("删除任务数据异常:", error); return false; } } } // 页面加载时可以自动调用查询接口加载数据 document.addEventListener("DOMContentLoaded", () => { // 可自动加载数据,或者等待用户点击查询 document.getElementById("btnQuery").click(); //renderHistasksTable(1); }); </script> {% endblock %}