@ -31,6 +31,10 @@
height: 45px;
overflow: hidden;
}
.offcanvas {
z-index: 1060 !important;
}
{% endblock %}
<!-- 页面内容块 -->
@ -38,7 +42,7 @@
< div class = "container-xxl mt-2" >
<!-- 查询区 -->
< div class = "row mb-3" >
< div class = "col-2 mb-2" > < button class = "btn btn-primary me-3" onclick = "openModal('add' )" > 导入< / button > < / div >
< div class = "col-2 mb-2" > < button class = "btn btn-primary me-3" onclick = "importModal( )" > 导入< / button > < / div >
< div class = "col-10" > < / div >
< div class = "col-3 mb-2" > < input type = "text" class = "form-control" id = "polltarget" placeholder = "检测目标" > < / div >
< div class = "col-3" > < input type = "text" class = "form-control" id = "owner" placeholder = "所属用户" > < / div >
@ -62,8 +66,8 @@
< / select >
< / div >
< div class = "col-2 text-end" >
< button class = "btn btn-primary" onclick = "fetchData( )" > 查询< / button >
< button class = "btn btn-primary" onclick = "exportOwner Data()" > 导出< / button >
< button class = "btn btn-primary" onclick = "loadPollingT(1 )" > 查询< / button >
< button class = "btn btn-primary" onclick = "exportPT Data()" > 导出< / button >
< / div >
< / div >
<!-- 表格 -->
@ -95,14 +99,500 @@
< / div >
<!-- 侧边 Drawer:选择所属用户 -->
< div class = "offcanvas offcanvas-start" tabindex = "-1" id = "ownerDrawer" >
< div class = "offcanvas-header" >
< h5 class = "offcanvas-title" > 选择所属用户< / h5 >
< button class = "btn-close" data-bs-dismiss = "offcanvas" > < / button >
< / div >
< div class = "offcanvas-body p-3" >
< div class = "input-group mb-3" >
< input type = "text" id = "ownerSearchKeyword" class = "form-control" placeholder = "搜索用户名…" >
< button class = "btn btn-outline-secondary" id = "btnSearchOwner" > 搜索< / button >
< / div >
< table class = "table table-sm align-middle" >
< thead >
< tr > < th style = "width:60px;" > 序号< / th > < th > 用户名< / th > < th style = "width:80px;" > 操作< / th > < / tr >
< / thead >
< tbody id = "ownerTableBody" > < / tbody >
< / table >
< / div >
< / div >
<!-- - - - - - - - - 导入modal - - - - - - - - - - -->
<!-- 导入目标 Modal -->
< div class = "modal fade" id = "importTargetModal" tabindex = "-1" aria-labelledby = "importTargetModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header border-0" >
< h4 class = "modal-title fw-bold" id = "importTargetModalLabel" > 导入目标< / h4 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "关闭" > < / button >
< / div >
< div class = "modal-body" >
< div class = "mb-3 d-flex justify-content-between align-items-center" >
< label class = "form-label fw-bold mb-0" > 巡检目标:< / label >
< button class = "btn btn-primary text-white" onclick = "importTargetTxt()" > 导入文件< / button >
< / div >
< textarea class = "form-control" rows = "8" style = "resize: none;" id = "pollingTarget" placeholder = "输入巡检目标。多目标以,(英文逗号)隔开,或导入txt文件(一行一个目标)" > < / textarea >
< input type = "file" id = "fileInput" accept = ".txt" style = "display:none;" / >
< div class = "row mt-3 align-items-center" >
< div class = "col-2" > < label class = "form-label fw-bold" > 所属用户:< / label > < / div >
< div class = "col-8" > < input type = "text" class = "form-control" id = "addtargetowner" placeholder = "所属用户" > < / div >
< div class = "col-2 d-flex justify-content-end" > < button type = "button" class = "btn btn-primary" onclick = "addOwner(1)" > 所属用户< / button > < / div >
< / div >
< / div >
< div class = "modal-footer border-0 d-flex justify-content-end" >
< button type = "button" class = "btn btn-primary me-3" onclick = "addPollingTarget()" > 确定< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > 关闭< / button >
< / div >
< / div >
< / div >
< / div >
<!-- - - - - - - - - 所属用户modal - - - - - - - - - - -->
< div class = "modal fade" id = "ownerModal" tabindex = "-1" aria-labelledby = "ownerModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header border-0" >
< h4 class = "modal-title fw-bold" id = "ownerModalLabel" > 所属用户< / h4 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "关闭" > < / button >
< / div >
< div class = "modal-body" >
< div class = "mb-3 d-flex justify-content-between align-items-center" >
< label class = "form-label fw-bold mb-0" > 所属用户:< / label >
< button class = "btn btn-primary text-white" onclick = "showOwner()" > 修改< / button >
< / div >
< input type = "text" class = "form-control" id = "now_owner" placeholder = "所属用户" >
< / div >
< div class = "modal-footer border-0 d-flex justify-content-end" >
< button type = "button" class = "btn btn-primary me-3" onclick = "upOwner()" > 确定< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > 关闭< / button >
< / div >
< / div >
< / div >
< / div >
<!-- - - - - - - - - 巡检策略modal - - - - - - - - - - -->
< div class = "modal fade" id = "strategyModal" tabindex = "-1" aria-labelledby = "strategyModalLabel" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-centered" >
< div class = "modal-content" >
< div class = "modal-header border-0" >
< h4 class = "modal-title fw-bold" id = "strategyModalLabel" > 巡检策略< / h4 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" aria-label = "关闭" > < / button >
< / div >
< div class = "modal-body" >
< div class = "row align-items-center mb-3" >
< div class = "col-2 text-end fw-bold" >
检测方案:
< / div >
< div class = "col-10" >
< select class = "form-select" id = "polling_type_Select" >
< option selected value = 0 > 请选择< / option >
< option value = 1 > 安全性检测< / option >
< option value = 2 > 可用性检测< / option >
< / select >
< / div >
< / div >
< div class = "row align-items-center mb-3" >
< div class = "col-2 text-end fw-bold" >
巡检周期:
< / div >
< div class = "col-10" >
< select class = "form-select" id = "pollint_period_Select" >
< option selected value = 0 > 请选择< / option >
< option value = 1 > 每日< / option >
< option value = 2 > 每周< / option >
< option value = 3 > 每月< / option >
< / select >
< / div >
< / div >
< div class = "row align-items-center mb-3" >
< div class = "col-2 text-end fw-bold" >
开始时间:
< / div >
< div class = "col-10" >
< input type = "text" class = "form-control" id = "startTimeInput" placeholder = "请选择开始时间" >
< / div >
< / div >
< / div >
< div class = "modal-footer border-0 justify-content-end" >
< button type = "button" class = "btn btn-primary me-2" id = "ipdatePTP" onclick = "savePTP()" > 保存< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > 关闭< / button >
< / div >
< / div >
< / div >
< / div >
{% endblock %}
<!-- 页面脚本块 -->
{% block script %}
< script src = "{{ url_for('main.static', filename='scripts/flatpickr') }}" > < / script >
< script >
//------------刷新页面表格-----------
const pollingT_tbody = document.querySelector('#pollingTable tbody');
const pT_prev = document.getElementById("pollingPrev");
const pT_next = document.getElementById("pollingNext");
let polling_targets = [],currentPTPage = 1,pageSize = 10;
let curTPlist_index = 0;
document.addEventListener("DOMContentLoaded", async () => {
// 初始加载
loadPollingT();
});
// 页面卸载时断开连接
window.addEventListener("beforeunload", function() {
polling_targets = [];
currentPTPage = 1;
});
// 分页按钮
pT_prev.addEventListener('click', e => { e.preventDefault(); randerPTTable(+e.target.dataset.page); });
pT_next.addEventListener('click', e => { e.preventDefault(); randerPTTable(+e.target.dataset.page); });
async function loadPollingT(page=1){
const PT = document.getElementById("polltarget").value.trim();
const owner = document.getElementById("owner").value.trim();
const PP = document.getElementById("polling_period").value;
const safe_rank = document.getElementById("risk_rank").value;
try {
const data = await postJSON("/api/assets/getpollingtarget",{PT,owner,PP,safe_rank})
polling_targets = data.pTargets || [];
randerPTTable(page); //刷新表格
} catch (error) {
console.error("查询巡检目标出错:", error);
alert("查询失败!"+error);
}
}
function getstrPollingType(itype){
strType = "";
if(itype === 1){
strType = "每天";
}else if(itype === 2){
strType = "每周";
}else if(itype === 3){
strType = "每月";
}
return strType;
}
function randerPTTable(page){
currentPTPage = page;
const start = (currentPTPage-1)*pageSize;
const slice = polling_targets.slice(start, start+pageSize);
pollingT_tbody.innerHTML = '';
for (let i=0; i< slice.length ; i + + ) {
const item = slice[i];
const tr = document.createElement('tr');
const strType = getstrPollingType(item[2])
tr.innerHTML = `
< td > ${start+i+1}< / td >
< td > ${item[0]}< / td >
< td > ${item[1]}< / td >
< td > ${strType}< / td >
< td > ${item[3]}< / td >
< td > ${item[4]}< / td >
< td >
< button class = "btn btn-sm btn-info asset-op-btn" onclick = "updateOwner('${start+i}')" > 所属用户< / button >
< button class = "btn btn-sm btn-info asset-op-btn" onclick = "PTConfig('${start+i}')" > 巡检策略< / button >
< button class = "btn btn-sm btn-danger asset-op-btn" onclick = "confirmDeletePT('${start+i}')" > 删除< / button >
< / td >
`;
pollingT_tbody.appendChild(tr);
}
// 补足空行
for (let i=slice.length; i< pageSize ; i + + ) {
const tr = document.createElement('tr');
tr.innerHTML = '< td colspan = "9" > < / td > ';
pollingT_tbody.appendChild(tr);
}
// 更新分页
pT_prev.dataset.page = currentPTPage>1?currentPTPage-1:1;
pT_next.dataset.page = pollingT_tbody.length>currentPTPage*pageSize?currentPTPage+1:currentPTPage;
}
//导出
function exportPTData(){
// 构造 CSV 文本
let rows = [
['序号', '检测目标', '所属用户', '检测周期', '最新检测时间','风险等级'].map(fmtCell).join(',')
];
polling_targets.forEach((row, i) => {
rows.push([
(i + 1).toString(),
row[0] || '',
row[1] || '',
row[2] || '',
row[3] || '',
row[4].toString(),
].map(fmtCell).join(','));
});
const csv = '\uFEFF' + rows.join('\r\n'); // 加 BOM
downloadCSV(csv, 'Polling_Target.csv');
}
//所属用户修改
function updateOwner(index){
select_data = polling_targets[index];
curTPlist_index = index;
addOwner(2); //类型为2
}
//删除目标
async function confirmDeletePT(index){
if (!confirm('确认删除?')) return;
select_data = polling_targets[index];
PT = select_data[0]
//提交接口
try {
const data = await postJSON("/api/assets/delPT",{PT});
bsuccess = data.bsuccess;
error = data.error;
if(bsuccess){
//更新数据
polling_targets.splice(index,1);
//刷新表格
randerPTTable(currentPTPage);
}
else {
alert("删除巡检目标失败:"+error);
}
} catch (error) {
console.error("删除巡检目标失败:", error);
alert("删除巡检目标失败!");
}
}
//-----------导入modal-------------
const importTargetModalEl = document.getElementById("importTargetModal");
const importTargetModal = new bootstrap.Modal(importTargetModalEl);
const addTargetEl = document.getElementById("pollingTarget"); //目标输入框
const fileInput = document.getElementById('fileInput');
const ownerEl = document.getElementById("addtargetowner")
//显示导入modal
function importModal(){
selectedOwnerId = null;
addTargetEl.value = "";
importTargetModal.show();
}
//导入txt文件
function importTargetTxt(){
fileInput.value = null; // 允许重复选择同一个文件
fileInput.click();
}
// 文件选中后读取内容、替换换行并填入输入框
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
// 现代浏览器支持直接用 File.text()
const text = await file.text();
// 按行拆分、去空行、再用英文逗号拼起来
const targets = text
.split(/\r?\n/) // 按 Unix/Windows 换行拆分
.map(line => line.trim()) // 去掉每行首尾空白
.filter(line => line) // 丢掉空行
.join(',');
// 填入测试目标输入框
addTargetEl.value = targets;
} catch (err) {
console.error('读取文件失败', err);
alert('读取文件失败,请检查文件格式');
}
});
//导入巡检目标,提交接口
async function addPollingTarget(){
pollind_targets = addTargetEl.value;
selectedOwnerName = ownerEl.value;
//提交接口
try {
const data = await postJSON("/api/assets/addpollingtarget",{pollind_targets,selectedOwnerName,selectedOwnerId});
success_list = data.success_list || [];
fail_list = data.fail_list || [];
let strsuc = success_list.join(',');
let strfail = fail_list.join(',');
let strout = "导入成功的有:\n" + strsuc + "\n导入失败的有:"+ strfail;
alert(strout);
importTargetModal.hide();
loadPollingT();
} catch (error) {
console.error("导入巡检目标出错:", error);
alert("导入巡检目标出错!");
}
}
//------------所属用户侧边栏---------------
const ownerDrawerEl = document.getElementById('ownerDrawer');
const ownerSearchEl = document.getElementById('ownerSearchKeyword');
const ownerTableBody = document.getElementById('ownerTableBody');
const btnSearchOwner = document.getElementById('btnSearchOwner');
const ownerDrawer = new bootstrap.Offcanvas(ownerDrawerEl);
let selectedOwnerId = null; // 当前选定的 user_id
let selectedOwnerName = null;
let owner_list = null; // 资产所属单位列表
let add_owner_type = 0; //修改所属用的来源 1-导入,2-列表
//显示所属用户侧边栏
function addOwner(itype){
add_owner_type = itype
ownerDrawer.show();
loadOwners(ownerSearchEl.value.trim());
}
//选择一个所属用户
ownerTableBody.addEventListener('click', e => {
const btn = e.target.closest('button[data-id]');
if (!btn) return;
selectedOwnerId = btn.dataset.id;
selectedOwnerName = btn.dataset.name;
if(add_owner_type === 1){
ownerEl.value = btn.dataset.name;
}
else {
//提交后台更新所属用户----需要删除用户没实现 --updatePTOwner PT owner_id
PT = polling_targets[curTPlist_index][0]
postupdateOwner(PT,selectedOwnerId)
}
ownerDrawer.hide();
});
//5.1--搜索按钮
btnSearchOwner.addEventListener('click', () => loadOwners(ownerSearchEl.value.trim()));
//调用接口获取所属用户列表
async function loadOwners(keyword='') {
try {
const res = await fetch('/api/assets/getassetsuser', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ keyword })
});
owner_list = (await res.json()).user_list;
ownerTableBody.innerHTML = '';
//ID,uname,tellnum,tell_username
owner_list.forEach((u, idx) => {
const tr = document.createElement('tr');
tr.innerHTML = `
< td > ${idx + 1}< / td >
< td > ${u[1]}< / td >
< td > < button class = "btn btn-link p-0" data-id = "${u[0]}" data-name = "${u[1]}"
data-tellnum="${u[2]}" data-telluname="${u[3]}">选择< / button > < / td > `;
ownerTableBody.appendChild(tr);
});
} catch (e) { showToast('加载用户列表失败', 'danger'); }
}
//----------所属用户-----------
async function postupdateOwner(PT,owner_id){
//提交接口
try {
const data = await postJSON("/api/assets/updatePTOwner",{PT,owner_id});
bsuccess = data.bsuccess;
error = data.error;
if(bsuccess){
//更新数据
polling_targets[curTPlist_index][1] = selectedOwnerName
//刷新表格
randerPTTable(currentPTPage);
}
else {
alert("修改所属用户失败:"+error);
}
} catch (error) {
console.error("修改所属用户失败:", error);
alert("修改所属用户失败!");
}
}
//-----------巡检策略---------
const ptpModalEl = document.getElementById("strategyModal")
const ptpModal = new bootstrap.Modal(ptpModalEl);
const ptEl = document.getElementById("polling_type_Select");
const ppEl = document.getElementById("pollint_period_Select");
const pstEl = document.getElementById("startTimeInput")
// 初始化时间控件
const fp = flatpickr("#startTimeInput", {
enableTime: true,
dateFormat: "Y-m-d H:i",
time_24hr: true,
});
//巡检策略配置--显示modal窗口
function PTConfig(index){
curTPlist_index = index;
select_data = polling_targets[index]; //t.scr_target,o.uname,t.polling_period,k.start_time,t.risk_rank,t.polling_type,t.polling_start_time
polling_type = select_data[5];
polling_period = select_data[2];
polling_start_time= select_data[6];
if(polling_type){
ptEl.value = polling_type;
}
if(polling_period){
ppEl.value = polling_period;
}
fp.setDate(polling_start_time)
ptpModal.show();
}
//保存巡检策略
async function savePTP(){
PT = polling_targets[curTPlist_index][0];
polling_type = parseInt(ptEl.value); //巡检类型
polling_period = parseInt(ppEl.value); //巡检周期
polling_start_time= pstEl.value; //巡检开始时间
// 将字符串转为时间对象
const selectedTime = new Date(polling_start_time);
const currentTime = new Date();
const localTimeStr = formatDateToLocalString(selectedTime);
if(polling_type === 0 || polling_period === 0 || isNaN(selectedTime.getTime())){
alert("巡检策略参数不能为空!");
return;
}
if (selectedTime < = currentTime) {
alert("开始时间必须大于当前时间!");
return;
}
//提交接口
try {
const data = await postJSON("/api/assets/updatePTPeriod",{PT,polling_type,polling_period,localTimeStr});
bsuccess = data.bsuccess;
error = data.error;
if(bsuccess){
//更新数据
polling_targets[curTPlist_index][2] = polling_period;
polling_targets[curTPlist_index][5] = polling_type;
polling_targets[curTPlist_index][6] = localTimeStr;
//刷新表格
randerPTTable(currentPTPage);
ptpModal.hide();
}else {
alert("修改巡检策略失败:"+error);
}
} catch (error) {
console.error("修改巡检策略失败:", error);
alert("修改巡检策略失败!"+error);
}
}
< / script >
{% endblock %}