{% extends 'base.html' %} {% block title %}ZFSAFE{% 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; } .offcanvas { z-index: 1060 !important; } {% endblock %} <!-- 页面内容块 --> {% block content %} <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="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> <div class="col-2"> <select class="form-select" id="polling_period"> <option value="">巡检周期</option> <option value="1">每日</option> <option value="2">每周</option> <option value="3">每月</option> </select> </div> <div class="col-2"> <select class="form-select" id="risk_rank"> <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> </select> </div> <div class="col-2 text-end"> <button class="btn btn-primary" onclick="loadPollingT(1)">查询</button> <button class="btn btn-primary" onclick="exportPTData()">导出</button> </div> </div> <!-- 表格 --> <div class="table-responsive"> <table class="table table-bordered table-hover" id="pollingTable" style="width: 100%; table-layout: fixed;"> <thead> <tr> <th style="width:5%;">序号</th> <th style="width:20%;">检测目标</th> <th style="width:20%;">所属用户</th> <th style="width:10%;">检测周期</th> <th style="width:15%;">最新检测时间</th> <th style="width:10%;">风险等级</th> <th style="width:20%;">操作</th> </tr> </thead> <tbody> <!-- JS 动态插入 10 行 --> </tbody> </table> </div> <!-- 分页 --> <nav class="mt-2"> <ul class="pagination justify-content-end" id="pollingPagination"> <li class="page-item"><a class="page-link" href="#" id="pollingPrev">上一页</a></li> <li class="page-item"><a class="page-link" href="#" id="pollingNext">下一页</a></li> </ul> </nav> </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 %}