const pageSize = 10; let ipAssets = [], currentIpPage = 1; document.addEventListener("DOMContentLoaded", async () => { // 初始加载 loadIpAssets(); loadUrlAssets(); }); // 页面卸载时断开连接 window.addEventListener("beforeunload", function() { ipAssets = []; urlAssets = []; currentIpPage = 1; }); //---------------------------IP-Assets Tab---------------------------- async function exportIpAssets(){ // 构造 CSV 文本 let rows = [ ['序号', '资产IP', '所属用户', '风险', '最新检测时间','端口数','关联域名'].map(fmtCell).join(',') ]; ipAssets.forEach((row, i) => { rows.push([ (i + 1).toString(), row[0].toString(), row[1] || '', row[2] || '', row[3] || '', row[4] || '', row[5] || '', ].map(fmtCell).join(',')); }); const csv = '\uFEFF' + rows.join('\r\n'); // 加 BOM downloadCSV(csv, `ip_assets.csv`); } async function loadIpAssets(page=1) { const IP = document.getElementById("ipFilter").value.trim(); const user = document.getElementById("userFilter").value.trim(); const safe_rank = document.getElementById("riskFilter").value; try { const data = await postJSON("/api/assets/getipassets",{IP,user,safe_rank}) ipAssets = data.ip_assets || []; renderIpTable(page); //刷新表格 } catch (error) { console.error("查询任务记录出错:", error); alert("查询失败!"); } } //刷新IP-assets表格 function renderIpTable(page) { currentIpPage = page; //查询数据了,从第一页显示 const start = (currentIpPage-1)*pageSize; const slice = ipAssets.slice(start, start+pageSize); const tbody = document.querySelector('#ipTable tbody'); tbody.innerHTML = ''; for (let i=0; i${start+i+1} ${item[0]} ${item[1]} ${item[2]} ${item[3]} ${item[4]} ${item[5]} `; tbody.appendChild(tr); } // 补足空行 for (let i=slice.length; i1?currentIpPage-1:1; document.getElementById('ipNext').dataset.page = ipAssets.length>currentIpPage*pageSize?currentIpPage+1:currentIpPage; } // 分页按钮 document.getElementById('ipPrev').addEventListener('click', e => { e.preventDefault(); renderIpTable(+e.target.dataset.page); }); document.getElementById('ipNext').addEventListener('click', e => { e.preventDefault(); renderIpTable(+e.target.dataset.page); }); // 查询 document.getElementById('ipSearchBtn').addEventListener('click', () => loadIpAssets(1)); //导出 document.getElementById("ipExportBtn").addEventListener('click',() => exportIpAssets()) //*********基本信息modal*********** const ipModalEl = document.getElementById('ipBasicInfoModal'); const ownerDrawerEl = document.getElementById('ownerDrawer'); const infoIpEl = document.getElementById('info_ip'); const infoRiskEl = document.getElementById('info_risk'); const infoOwnerEl = document.getElementById('info_owner'); const infoScanTimeEl = document.getElementById('info_scanTime'); const contactNameEl = document.getElementById('contactName'); const contactPhoneEl = document.getElementById('contactPhone'); const btnChooseOwner = document.getElementById('btnChooseOwner'); const btnSaveIpInfo = document.getElementById('saveIpInfo'); const ownerSearchEl = document.getElementById('ownerSearchKeyword'); const btnSearchOwner = document.getElementById('btnSearchOwner'); const btnDelOwner = document.getElementById("btnDelOwner"); const ownerTableBody = document.getElementById('ownerTableBody'); const ipinofModal = new bootstrap.Modal(ipModalEl); const ownerDrawer = new bootstrap.Offcanvas(ownerDrawerEl); let selectedOwnerId = null; // 当前选定的 user_id let owner_list = null; // 资产所属单位列表 let curModal; //选择所属用户界面的共享参数,用于区分时IP窗口进入,还是URL窗口进入 // 5. Drawer 操作 btnChooseOwner.addEventListener('click', () => { curModal = "ip" ownerDrawer.show(); loadOwners(ownerSearchEl.value.trim()); }); //删除所属用户的关系 btnDelOwner.addEventListener('click',() =>{ selectedOwnerId = null; infoOwnerEl.textContent = ''; contactNameEl.textContent = ''; contactPhoneEl.textContent = ''; }); //选择一个所属用户 ownerTableBody.addEventListener('click', e => { const btn = e.target.closest('button[data-id]'); if (!btn) return; selectedOwnerId = btn.dataset.id; if(curModal === "ip"){ infoOwnerEl.textContent = btn.dataset.name; contactNameEl.textContent = btn.dataset.telluname; contactPhoneEl.textContent = btn.dataset.tellnum; }else{ ownerEl.textContent = btn.dataset.name; contactEl.textContent = btn.dataset.telluname; tellEl.textContent = btn.dataset.tellnum; } 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 = ` ${idx + 1} ${u[1]} `; ownerTableBody.appendChild(tr); }); } catch (e) { showToast('加载用户列表失败', 'danger'); } } // 6. 保存修改 btnSaveIpInfo.addEventListener('click', async () => saveIpInfo()); // 7. hidden 清理 ipModalEl.addEventListener('hidden.bs.modal', () => { selectedOwnerId = null; document.getElementById('ipInfoForm').reset(); ['info_ip','info_risk','info_owner','info_scanTime'].forEach(id => { document.getElementById(id).textContent = ''; }); ownerDrawer.hide(); }); //显示基本信息 async function showBasicInfo(IP,owner,risk_rank,update_time) { try { infoIpEl.textContent = IP; infoRiskEl.textContent = risk_rank; infoOwnerEl.textContent = owner; infoScanTimeEl.textContent = update_time; if(owner == 'null'){ contactNameEl.textContent = ''; contactPhoneEl.textContent = ''; } else { //au.ID,au.tellnum,au.tell_username try{ const data = await postJSON('/api/assets/getipinfo', {IP}); ip_info = data.ip_info; } catch (error) { ip_info = [] console.error("获取IP资产基本信息失败!",error); } contactNameEl.textContent = ip_info[2] || ''; contactPhoneEl.textContent = ip_info[1] || ''; selectedOwnerId = ip_info[0] || 0; } ipinofModal.show(); } catch (e) { showToast(`请求基本信息失败:${e.message}`, 'danger'); } } //保存所属用户的基本信息 async function saveIpInfo(){ const payload = { cur_ip: infoIpEl.textContent, owner_id: selectedOwnerId }; try { const ret = await postJSON('/api/assets/updateipinfo', payload); bsuccess = ret.bsuccess; if (bsuccess) { showToast('修改成功', 'success'); //bootstrap.Modal.getInstance(ipModalEl).hide(); loadIpAssets(currentIpPage) //刷新整个数据。。 } else { showToast(ret.message || '修改失败', 'danger'); } } catch (e) { showToast(`提交失败:${e.message}`, 'danger'); } } //***********端口数据************** const latestTableBody = document.querySelector('#latestPortTable tbody'); const latestPrevBtn = document.getElementById('latestPrev'); const latestNextBtn = document.getElementById('latestNext'); const historyTableHead = document.querySelector('#historyPortTable thead'); const historyTableBody = document.querySelector('#historyPortTable tbody'); const hisPortPrevBtn = document.getElementById('hisPortPrev'); const hisPortNextBtn = document.getElementById('hisPortNext'); const portModalEl = document.getElementById('portDataModal'); const portModal = new bootstrap.Modal(portModalEl); let latestData = [], historyData = {}; let latestPage = 1, latestPageSize = 10; async function showPortData(IP){ // 把 IP 暂存到 modal 元素上 portModalEl.dataset.ip = IP; portModal.show() } // 打开 Modal 时加载 portModalEl.addEventListener('show.bs.modal', async ev => { const ip = portModalEl.dataset.ip; if (!ip) return; // 1. 拉最新数据 try{ const latestJson = await postJSON('/api/assets/getportlatest', {ip}); latestData = latestJson.port_data || []; } catch (error) { console.error("拉取最新的端口数据失败!",error) } renderLatest(); // 2. 拉历史数据 try{ const histJson = await postJSON('/api/assets/getporthistory', {ip}); historyData = histJson || []; } catch (error) { console.error("拉取历史的端口数据失败!",error) } renderHistory(); }); // 渲染最新数据和分页 function renderLatest() { latestTableBody.innerHTML = ''; const start = (latestPage-1)*latestPageSize; const slice = latestData.slice(start, start+latestPageSize); slice.forEach((row,i) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${start+i+1} ${row[0]} ${row[1]} ${row[2]} ${row[3]} `; latestTableBody.appendChild(tr); }); // 补空行 for (let i = slice.length; i < latestPageSize; i++) { const tr = document.createElement('tr'); tr.innerHTML = ' '; latestTableBody.appendChild(tr); } // 更新按钮 latestPrevBtn.dataset.page = latestPage>1?latestPage-1:1; latestNextBtn.dataset.page = latestData.length>latestPage*latestPageSize?latestPage+1:latestPage; } latestPrevBtn.addEventListener('click', e => { e.preventDefault(); latestPage = +e.target.dataset.page; renderLatest(); }); latestNextBtn.addEventListener('click', e => { e.preventDefault(); latestPage = +e.target.dataset.page; renderLatest(); }); // 渲染历史数据 function renderHistory() { historyTableHead.innerHTML = ''; historyTableBody.innerHTML = ''; // 1) 表头:批次时间 const th = document.createElement('tr'); th.innerHTML = `端口 / 时间` + historyData.times.map(t => `${t}`).join(''); historyTableHead.appendChild(th); imaxcount = historyData.times.length; // 2) 每个端口一行 Object.entries(historyData.entries).forEach(([port, info]) => { const tr = document.createElement('tr'); // 第一列:端口号 let rowHtml = `${port}`; // 后面的各批次状态i let idx = 0; for(let i=1;i <= imaxcount;i++){ if(info.scancount[idx] == i){ // 若 changed 为 1,则背景淡红 const bg = info.changed[idx] ? 'background-color:#ffecec;' : ''; // 鼠标移上时显示服务/版本/协议 const title = `服务: ${info.service[idx]}\n` + `版本: ${info.version[idx]}`; rowHtml += `${info.statuses[idx]}`; idx ++; } else {//序号不对则加个空单元格 rowHtml += ``; } } tr.innerHTML = rowHtml; historyTableBody.appendChild(tr); }); } // 导出按钮 document.getElementById('exportPortData').onclick = () => { // 找到当前激活的 tab-pane const activePane = document.querySelector('#portTabContent .tab-pane.active').id; if (activePane === 'latestPane') { // 导出最新数据:可以直接把 latestData 当前页 slice 成 CSV exportLatest(); } else { // 导出历史数据 exportHistory(); } }; function exportLatest() { // 构造 CSV 文本 let rows = [ ['序号', '端口号', '服务', '版本号', '端口状态'].map(fmtCell).join(',') ]; latestData.forEach((row, i) => { rows.push([ (i + 1).toString(), row[0].toString(), row[1] || '', row[2] || '', row[3] || '' ].map(fmtCell).join(',')); }); const csv = '\uFEFF' + rows.join('\r\n'); // 加 BOM downloadCSV(csv, `latest_ports_${portModalEl.dataset.ip}.csv`); } function exportHistory() { const thead = historyTableHead.querySelector('tr'); const tbody = historyTableBody.querySelectorAll('tr'); // 1. 构造表头 CSV const headerCells = Array.from(thead.children).map(th => fmtCell(th.textContent.trim())); const headerLine = headerCells.join(','); // 2. 构造表体 CSV const bodyLines = Array.from(tbody).map(tr => { const cells = Array.from(tr.children).map(td => fmtCell(td.textContent.trim())); return cells.join(','); }); // 3. 拼接,加 BOM const csvContent = '\uFEFF' + [headerLine, ...bodyLines].join('\r\n'); // 4. 发起下载 downloadCSV(csvContent, `history_ports_${portModalEl.dataset.ip}.csv`); } //***********漏洞数据************** const vulModalEl = document.getElementById('vulDataModal'); const nodeNameEl = document.getElementById("vulNodeName"); const vulTypeEl = document.getElementById("vulType"); const vulLevelEl = document.getElementById("vulLevel"); const vulModal = new bootstrap.Modal(vulModalEl); let allVuls = []; let currentVulPage = 1; async function showVulData(IP){ vulModalEl.dataset.ip = IP; nodeNameEl.value = ""; vulTypeEl.value = ""; vulLevelEl.value = ""; vulModal.show(); } vulModalEl.addEventListener('show.bs.modal',async ev =>{ searchVulnerabilities(); }); async function searchVulnerabilities(page = 1) { const ip = vulModalEl.dataset.ip; if (!ip) return; const nodeName = nodeNameEl.value.trim(); const vulType = vulTypeEl.value.trim(); const vulLevel = vulLevelEl.value; //拉取该IP最新完成检测的漏洞数据 try{ const vuldata = await postJSON('/api/assets/getvuldata', {ip,nodeName,vulType,vulLevel}); allVuls = vuldata.vuls || []; console.log("拉取漏洞数据") } catch (error) { console.error("拉取漏洞数据失败!",error) } renderVulPage(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("vulTable").scrollIntoView({ behavior: "smooth" }); // 更新分页按钮 document.getElementById("vulPrev").dataset.page = page > 1 ? page - 1 : 1; document.getElementById("vulNext").dataset.page = (end < allVuls.length) ? page + 1 : page; } //使用该函数,需要数据的顺序与表格列的数据一致 function renderTableRows(tbody, rowsData) { tbody.innerHTML = ""; // 遍历数据行,生成 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 = " "; tr.appendChild(td); } tbody.appendChild(tr); } } //导出漏洞数据 async function ExportVuls(){ // Check if in secure context if (!window.isSecureContext) { console.error("Page is not in a secure context. Please load the page over HTTPS."); alert("无法导出文件:请使用 HTTPS 访问页面。"); return; } // 如果 dataArr 为空,返回提示 if (!allVuls || allVuls.length === 0) { alert("没有数据可导出"); return; } // 在 CSV 的开头加上 BOM,用于 Excel 识别 UTF-8 编码 let csv = "\uFEFF" + "序号,节点路径,漏洞类型,漏洞级别,漏洞说明\n"; // 添加 BOM 防乱码 allVuls.forEach((item, i) => { csv += [ escapeCsvField(item[0] || ""), escapeCsvField(item[1] || ""), escapeCsvField(item[2] || ""), escapeCsvField(item[3] || ""), escapeCsvField(item[4] || ""), ].join(",") + "\n"; }); const blob = new Blob([csv], { 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); } // 绑定漏洞数据查询按钮事件 document.getElementById("vulSearchBtn").addEventListener("click", () => { searchVulnerabilities(); }); //导出按钮 document.getElementById("vulExportBtn").addEventListener("click",()=>ExportVuls()); // 绑定漏洞数据分页点击事件 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); }); //***********关联域名************** let latestUrls = [], historyUrls = []; let urllatestPage = 1, urlhistoryPage = 1; const urlModalEl = document.getElementById('urlDataModal'); const urlModal = new bootstrap.Modal(urlModalEl); // table bodies & pagination const latestTbody = document.querySelector('#latestUrlTable tbody'); const latestPrev = document.getElementById('latestUrlPrev'); const latestNext = document.getElementById('latestUrlNext'); const historyTbody = document.querySelector('#historyUrlTable tbody'); const historyPrev = document.getElementById('historyUrlPrev'); const historyNext = document.getElementById('historyUrlNext'); // 打开 Modal 时加载 urlModalEl.addEventListener('show.bs.modal', async ev => { const ip = urlModalEl.dataset.ip; if (!ip) return; //拉 最新关联 try{ const latestJson = await postJSON('/api/assets/geturllatest', {ip}); latestUrls = latestJson.ip_url_data || []; } catch (error) { console.error("拉取最新关联的url数据失败!",error) } urllatestPage = 1; urlrenderLatest(); //拉 历史关联 try{ const hisJson = await postJSON('/api/assets/geturlhistory', {ip}); historyUrls = hisJson.ip_url_data || []; } catch (error) { console.error("拉取历史的关联URL数据失败!",error) } urlhistoryPage = 1; urlrenderHistory(); }); function urlrenderLatest() { latestTbody.innerHTML = ''; const start = (urllatestPage - 1) * pageSize; const slice = latestUrls.slice(start, start + pageSize); slice.forEach((it, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${start+idx+1} ${it[0]} ${it[1]} ${it[2]} ${it[3]} ${it[4]} ${it[5]} `; latestTbody.appendChild(tr); }); // 空行 for (let i=slice.length; i1?urllatestPage-1:1; latestNext.dataset.page = latestUrls.length>urllatestPage*pageSize?urllatestPage+1:urllatestPage; } function urlrenderHistory() { historyTbody.innerHTML = ''; const start = (urlhistoryPage - 1) * pageSize; const slice = historyUrls.slice(start, start + pageSize); slice.forEach((it, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${start+idx+1} ${it[2]} ${it[0]} ${it[1]} `; historyTbody.appendChild(tr); }); for (let i=slice.length; i1?urlhistoryPage-1:1; historyNext.dataset.page = historyUrls.length>urlhistoryPage*pageSize?urlhistoryPage+1:urlhistoryPage; } // 分页绑定 latestPrev.addEventListener('click',e=>{e.preventDefault(); urllatestPage=+e.target.dataset.page; renderLatest();}); latestNext.addEventListener('click',e=>{e.preventDefault(); urllatestPage=+e.target.dataset.page; renderLatest();}); historyPrev.addEventListener('click',e=>{e.preventDefault(); urlhistoryPage=+e.target.dataset.page; renderHistory();}); historyNext.addEventListener('click',e=>{e.preventDefault(); urlhistoryPage=+e.target.dataset.page; renderHistory();}); // 打开函数 window.showRelatedDomain = function(ip) { urlModalEl.dataset.ip = ip; urlModal.show(); }; // 导出(示例 CSV,仅最新/历史当前页) document.getElementById('exportUrlData').onclick = () => { const active = document.querySelector('#urlTabContent .tab-pane.active').id; if (active==='urlLatestPane') { let rows=[['序号','域名','子域名','注册人','注册邮箱','创建时间','过期时间']]; latestUrls.forEach((it,i)=>{ rows.push([i+1,it[0],it[1],it[2],it[3],it[4],it[5]]); }); urldownloadCSV(rows,'最新关联域名.csv'); } else { let rows=[['序号','时间','变化类型','关联域名']]; historyUrls.forEach((it,i)=>{ rows.push([i+1,it[2],it[0],it[1]]); }); urldownloadCSV(rows,'历史关联域名.csv'); } }; function urldownloadCSV(arr, fname) { const bom='\uFEFF'; const csv = arr.map(r => r.map(v=>`"${v}"`).join(',')).join('\r\n'); const blob = new Blob([bom+csv], {type:'text/csv;charset=utf-8;'}); const a = document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=fname; a.click(); URL.revokeObjectURL(a.href); } //***********删除************** async function confirmDeleteIp(IP) { if (!confirm('确认删除?')) return; try{ const redata = await postJSON('/api/assets/delipassets', {IP}); bsuccess = redata.bsuccess; error = redata.error; if(bsuccess){ alert("删除IP资产成功!"); loadIpAssets(); //刷新数据 --重新拉取数据后刷新,可以考虑本地数据修改后刷新页面。 } else{ alert("删除IP资产失败!",error) } } catch (error) { console.error("删除IP资产失败!",error); } } //---------------------------URL-Assets Tab---------------------------- const urlInfoModalEl = document.getElementById("urlBasicInfoModal") const urlInfoModal = new bootstrap.Modal(urlInfoModalEl); let urlAssets = [],currentUrlPage = 1 async function loadUrlAssets(page=1) { const url_filter = document.getElementById("urlFilter").value.trim(); const user_filter = document.getElementById("ownerFilter").value.trim(); const email_filter = document.getElementById("emailFilter").value; try { const data = await postJSON("/api/assets/geturlassets",{url_filter,user_filter,email_filter}) urlAssets = data.url_assets || []; renderUrlTable(page); //刷新表格 } catch (error) { console.error("查询记录出错:", error); alert("查询失败!"); } } //刷新Url-assets表格 function renderUrlTable(page) { currentUrlPage = page; //查询数据了,从第一页显示 const start = (currentUrlPage-1)*pageSize; const slice = urlAssets.slice(start, start+pageSize); const tbody = document.querySelector('#urlTable tbody'); tbody.innerHTML = ''; for (let i=0; i${start+i+1} ${item[1]} ${item[2]} ${item[3]} ${item[4]} ${item[5]} ${item[6]} `; tbody.appendChild(tr); } // 补足空行 for (let i=slice.length; i1?currentUrlPage-1:1; document.getElementById('urlNext').dataset.page = urlAssets.length>currentUrlPage*pageSize?currentUrlPage+1:currentUrlPage; } //导出域名信息 async function exportUrlAssets(){ // 构造 CSV 文本 let rows = [ ['序号', '域名', '所属用户', '注册邮箱', '最新检测时间','过期日期','IP'].map(fmtCell).join(',') ]; urlAssets.forEach((row, i) => { rows.push([ (i + 1).toString(), row[1].toString(), row[2] || '', row[3] || '', row[4] || '', row[5] || '', row[6] || '', ].map(fmtCell).join(',')); }); const csv = '\uFEFF' + rows.join('\r\n'); // 加 BOM downloadCSV(csv, `url_assets.csv`); } // 分页按钮 document.getElementById('urlPrev').addEventListener('click', e => { e.preventDefault(); renderUrlTable(+e.target.dataset.page); }); document.getElementById('urlNext').addEventListener('click', e => { e.preventDefault(); renderUrlTable(+e.target.dataset.page); }); // 查询 document.getElementById('urlSearchBtn').addEventListener('click', () => loadUrlAssets(1)); //导出 document.getElementById("urlExportBtn").addEventListener('click',() => exportUrlAssets()) //***********URL基本信息modal************** const urlEl = document.getElementById("info_url"); const timeEl = document.getElementById("url_scanTime"); const registrantEl = document.getElementById("url_register"); const emailEl = document.getElementById("url_email"); const ownerEl = document.getElementById("url_owner"); const contactEl = document.getElementById("urlcontactName"); const tellEl = document.getElementById("urlcontactPhone"); const btnChose = document.getElementById("urlbtnChooseOwner"); const btnDelO = document.getElementById("urlbtnDelOwner"); const btnSaveurl = document.getElementById("saveUrlInfo"); let cur_url_id = 0; //url.ID,url.URL,au.uname,url.emails,url.update_time,url.expiration_date,ip.ip_count,url.Registrant,au.tellnum,au.tell_username async function urlBasicInfo(index){ const url_info = urlAssets[index]; cur_url_id = url_info[0]; urlEl.textContent = url_info[1]; timeEl.textContent = url_info[4]; registrantEl.textContent = url_info[7]; emailEl.textContent = url_info[3]; ownerEl.textContent = url_info[2]; contactEl.textContent = url_info[9]; tellEl.textContent = url_info[8]; selectedOwnerId = url_info[10]; urlInfoModal.show(); } btnChose.addEventListener("click",()=>{ //和IP共用一个模态框 curModal = "url" ownerDrawer.show(); loadOwners(ownerSearchEl.value.trim()); }); btnDelO.addEventListener("click",()=>{ selectedOwnerId = null; ownerEl.textContent = ''; contactEl.textContent = ''; tellEl.textContent = ''; }); btnSaveurl.addEventListener("click",async ()=>saveUrlInfo()); async function saveUrlInfo(){ const payload = { cur_url_id: cur_url_id, owner_id: selectedOwnerId }; try { const ret = await postJSON('/api/assets/updateurlinfo', payload); bsuccess = ret.bsuccess; if (bsuccess) { showToast('修改成功', 'success'); loadUrlAssets(currentIpPage); //刷新整个数据。。--要改成局部刷新或本地刷新 } else { showToast(ret.message || '修改失败', 'danger'); } } catch (e) { showToast(`提交失败:${e.message}`, 'danger'); } } //***********指向IPmodal************** const urlIPModalEl = document.getElementById("toIpModal"); const urlIPModal = new bootstrap.Modal(urlIPModalEl); let last_to_ips = [],his_to_ips=[]; let last_to_ips_page = 1, his_to_ip_page = 1; const latestUTPTableBody = document.querySelector('#latesttoipTable tbody'); const latestUTPPrevBtn = document.getElementById('latesttoipPrev'); const latestUTPNextBtn = document.getElementById('latesttoipNext'); const historyutpTableBody = document.querySelector('#historytoipTable tbody'); const hisPortUTPPrevBtn = document.getElementById('historytoipPrev'); const hisPortUTPNextBtn = document.getElementById('historytoipNext'); const exportUTPDataBtn = document.getElementById("exporttoipData"); exportUTPDataBtn.addEventListener('click',()=>exportUTPdata()); async function urlIPData(cur_url_id){ //获取数据 const payload ={cur_url_id}; try { const ret = await postJSON('/api/assets/geturltoIP', payload); last_to_ips = ret.last_to_ips; his_to_ips = ret.his_to_ips; renderUTPlast(); renderUTPhis(); urlIPModal.show(); } catch (e) { showToast(`获取指向IP数据失败:${e.message}`, 'danger'); } } function renderUTPlast(){ latestUTPTableBody.innerHTML = ''; const start = (last_to_ips_page - 1) * pageSize; const slice = last_to_ips.slice(start, start + pageSize); slice.forEach((it, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${start+idx+1} ${it[0]} ${it[1]} `; latestUTPTableBody.appendChild(tr); }); // 空行 for (let i=slice.length; i1?last_to_ips_page-1:1; latestUTPNextBtn.dataset.page = last_to_ips.length>last_to_ips_page*pageSize?last_to_ips_page+1:last_to_ips_page; } function renderUTPhis(){ historyutpTableBody.innerHTML = ''; const start = (his_to_ip_page - 1) * pageSize; const slice = his_to_ips.slice(start, start + pageSize); slice.forEach((it, idx) => { const tr = document.createElement('tr'); tr.innerHTML = ` ${start+idx+1} ${it[0]} ${it[1]} `; historyutpTableBody.appendChild(tr); }); // 空行 for (let i=slice.length; i1?his_to_ip_page-1:1; hisPortUTPNextBtn.dataset.page = his_to_ips.length>his_to_ip_page*pageSize?his_to_ip_page+1:his_to_ip_page; } // 分页绑定 latestUTPPrevBtn.addEventListener('click',e=>{e.preventDefault(); last_to_ips_page=+e.target.dataset.page; renderLatest();}); latestUTPNextBtn.addEventListener('click',e=>{e.preventDefault(); last_to_ips_page=+e.target.dataset.page; renderLatest();}); hisPortUTPPrevBtn.addEventListener('click',e=>{e.preventDefault(); his_to_ip_page=+e.target.dataset.page; renderHistory();}); hisPortUTPNextBtn.addEventListener('click',e=>{e.preventDefault(); his_to_ip_page=+e.target.dataset.page; renderHistory();}); //导出指向IP的数据 async function exportUTPdata(){ // 构造 CSV 文本 const active = document.querySelector('#toipTabContent .tab-pane.active').id; if (active==='toipLatestPane') { let rows=[['序号','IP地址','关联时间']]; last_to_ips.forEach((it,i)=>{ rows.push([i+1,it[0],it[1]]); }); urldownloadCSV(rows,'当前指向IP的数据.csv'); } else { let rows=[['序号','IP地址','取关时间']]; his_to_ips.forEach((it,i)=>{ rows.push([i+1,it[0],it[1]]); }); urldownloadCSV(rows,'历史指向IP的数据.csv'); } } //***********删除URL************** async function confirmDeleteUrl(url,index){ if (!confirm('确认删除?')) return; try{ const redata = await postJSON('/api/assets/delurlassets', {url}); bsuccess = redata.bsuccess; error = redata.error; if(bsuccess){ alert("删除域名资产成功!"); //loadUrlAssets(); //刷新数据 //只更新本地数据 urlAssets.splice(index,1); renderUrlTable(currentUrlPage);//刷新页面 --不重新请求数据了 } else{ alert("删除域名资产失败!",error); } } catch (error) { console.error("删除域名资产失败!",error); } }