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.
 
 
 
 

974 lines
34 KiB

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<slice.length; i++) {
const item = slice[i];
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${start+i+1}</td>
<td>${item[0]}</td>
<td>${item[1]}</td>
<td>${item[2]}</td>
<td>${item[3]}</td>
<td>${item[4]}</td>
<td>${item[5]}</td>
<td>
<button class="btn btn-sm btn-info asset-op-btn" onclick="showBasicInfo('${item[0]}','${item[1]}','${item[2]}','${item[3]}')">基本信息</button>
<button class="btn btn-sm btn-info asset-op-btn" data-ip="${item[0]}" onclick="showPortData('${item[0]}')">端口数据</button>
<button class="btn btn-sm btn-info asset-op-btn" onclick="showVulData('${item[0]}')">漏洞数据</button>
<button class="btn btn-sm btn-info asset-op-btn" onclick="showRelatedDomain('${item[0]}')">关联域名</button>
<button class="btn btn-sm btn-danger asset-op-btn" onclick="confirmDeleteIp('${item[0]}')">删除</button>
</td>
`;
tbody.appendChild(tr);
}
// 补足空行
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="9">&nbsp;</td>';
tbody.appendChild(tr);
}
// 更新分页
document.getElementById('ipPrev').dataset.page = currentIpPage>1?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 = `
<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'); }
}
// 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 = `
<td>${start+i+1}</td>
<td>${row[0]}</td>
<td>${row[1]}</td>
<td>${row[2]}</td>
<td>${row[3]}</td>
`;
latestTableBody.appendChild(tr);
});
// 补空行
for (let i = slice.length; i < latestPageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="5">&nbsp;</td>';
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 = `<th style="width:80px">端口 / 时间</th>` +
historyData.times.map(t => `<th style="width:100px">${t}</th>`).join('');
historyTableHead.appendChild(th);
imaxcount = historyData.times.length;
// 2) 每个端口一行
Object.entries(historyData.entries).forEach(([port, info]) => {
const tr = document.createElement('tr');
// 第一列:端口号
let rowHtml = `<td>${port}</td>`;
// 后面的各批次状态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 += `<td style="${bg}" title="${title}">${info.statuses[idx]}</td>`;
idx ++;
} else {//序号不对则加个空单元格
rowHtml += `<td style="background-color: lightsteelblue"></td>`;
}
}
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 = "";
// 遍历数据行,生成 <tr>
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 = "&nbsp;";
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 = `
<td>${start+idx+1}</td>
<td>${it[0]}</td>
<td>${it[1]}</td>
<td>${it[2]}</td>
<td>${it[3]}</td>
<td>${it[4]}</td>
<td>${it[5]}</td>
`;
latestTbody.appendChild(tr);
});
// 空行
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="7">&nbsp;</td>';
latestTbody.appendChild(tr);
}
latestPrev.dataset.page = urllatestPage>1?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 = `
<td>${start+idx+1}</td>
<td>${it[2]}</td>
<td>${it[0]}</td>
<td>${it[1]}</td>
`;
historyTbody.appendChild(tr);
});
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="7">&nbsp;</td>';
historyTbody.appendChild(tr);
}
historyPrev.dataset.page = urlhistoryPage>1?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<slice.length; i++) {
const item = slice[i];
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${start+i+1}</td>
<td>${item[1]}</td>
<td>${item[2]}</td>
<td>${item[3]}</td>
<td>${item[4]}</td>
<td>${item[5]}</td>
<td>${item[6]}</td>
<td>
<button class="btn btn-sm btn-info asset-op-btn" onclick="urlBasicInfo('${start+i}')">基本信息</button>
<button class="btn btn-sm btn-info asset-op-btn" onclick="urlIPData('${item[0]}')">指向IP</button>
<button class="btn btn-sm btn-danger asset-op-btn" onclick="confirmDeleteUrl('${item[0]}','${start+i}')">删除</button>
</td>
`;
tbody.appendChild(tr);
}
// 补足空行
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="9">&nbsp;</td>';
tbody.appendChild(tr);
}
// 更新分页
document.getElementById('urlPrev').dataset.page = currentUrlPage>1?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 = `
<td>${start+idx+1}</td>
<td>${it[0]}</td>
<td>${it[1]}</td>
`;
latestUTPTableBody.appendChild(tr);
});
// 空行
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="7">&nbsp;</td>';
latestUTPTableBody.appendChild(tr);
}
latestUTPPrevBtn.dataset.page = last_to_ips_page>1?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 = `
<td>${start+idx+1}</td>
<td>${it[0]}</td>
<td>${it[1]}</td>
`;
historyutpTableBody.appendChild(tr);
});
// 空行
for (let i=slice.length; i<pageSize; i++) {
const tr = document.createElement('tr');
tr.innerHTML = '<td colspan="7">&nbsp;</td>';
historyutpTableBody.appendChild(tr);
}
hisPortUTPPrevBtn.dataset.page = his_to_ip_page>1?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);
}
}