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.
 
 
 
 

661 lines
25 KiB

{% 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;
}
/* 模态框内部最大高度,超出部分滚动 */
.modal-dialog {
max-height: calc(100vh+20px);
}
.modal-content {
max-height: calc(100vh+20px);
}
.modal-body {
overflow-y: auto;
}
/* 这里设置页码按钮样式(可根据需要调整) */
.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>
</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: 60px;">ID</th>
<th style="width: 20%;">检测目标</th>
<th style="width: 15%;">开始时间</th>
<th style="width: 15%;">结束时间</th>
<th style="width: 15%;">风险等级</th>
<th style="width: 15%;">使用模型</th>
<th style="width: 100px;">操作</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 h-100" id="nodeTree" role="tabpanel" aria-labelledby="nodeTreeTab">
<div class="row h-100">
<!-- 左侧:节点树区域 -->
<div class="col-8 h-100">
<div class="node-tree-area" id="nodeTreeContainer" style="height: 100%; overflow-y: auto; position: relative; background-color: #f8f9fa;">
<!-- 固定刷新按钮 -->
<!-- <div class="refresh-container" style="position: absolute; top: 5px; left: 5px; z-index: 100;">-->
<!-- <button class="tree-refresh btn btn-primary btn-sm" id="btnRefresh" title="刷新节点树">&#x21bb;</button>-->
<!-- </div>-->
<!-- 节点树内容 -->
<div id="treeContent" class="tree-content" style="padding-top: 40px;">
<p id="treeLoadingMsg" style="text-align:center;">加载中...</p>
</div>
</div>
</div>
<!-- 右侧:节点信息与操作 -->
<div class="col-4 h-100">
<div class="node-info-area mb-3" 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_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/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{
model_test="其他模型";
}
tdModel.textContent = model_test;
tr.appendChild(tdModel);
const tdAction = document.createElement("td");
// 查看按钮(点击后弹出 modal)
const btnView = document.createElement("button");
btnView.className = "btn btn-outline-info btn-sm";
btnView.textContent = "查看";
btnView.onclick = () => openViewModal(task[0]);
tdAction.appendChild(btnView);
// 删除按钮(示例)
const btnDel = document.createElement("button");
btnDel.className = "btn btn-outline-danger btn-sm ms-1";
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 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();
//查询节点树数据
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 %}