feat(ips): 批量补全厂商功能 + MAC厂商替换为IEEE OUI数据库
前端: - IPs.vue: 新增「补全厂商」按钮,选中网段后一键补全 - scanApi: 新增 batchFillVendor/batchDiscoverHostname + enable_netbios 参数 后端: - enhanced_scan.py: 新增 POST /scan/batch/vendor 批量补全厂商端点 - enhanced_scan.py: 新增 POST /scan/batch/hostname 批量发现主机名端点 - scan_tasks.py: 透传 enable_netbios 参数
This commit is contained in:
@@ -156,6 +156,82 @@ def netbios_lookup(ip_address: str, timeout: int = 3):
|
|||||||
"hostname": hostname or "Unknown"
|
"hostname": hostname or "Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@router.post("/batch/vendor", summary="批量补全厂商信息")
|
||||||
|
def batch_fill_vendor(
|
||||||
|
network_id: Optional[int] = None,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
对已有 MAC 地址但缺少厂商信息的 IP 批量补全厂商(使用 IEEE OUI 数据库)
|
||||||
|
"""
|
||||||
|
from app.models.network import IPAddress as IPAddressModel
|
||||||
|
|
||||||
|
query = db.query(IPAddressModel).filter(
|
||||||
|
IPAddressModel.mac_address.isnot(None),
|
||||||
|
IPAddressModel.vendor.is_(None)
|
||||||
|
)
|
||||||
|
if network_id:
|
||||||
|
query = query.filter(IPAddressModel.network_id == network_id)
|
||||||
|
|
||||||
|
items = query.all()
|
||||||
|
updated_count = 0
|
||||||
|
|
||||||
|
for ip in items:
|
||||||
|
vendor = EnhancedScanService.get_mac_vendor(ip.mac_address)
|
||||||
|
if vendor:
|
||||||
|
ip.vendor = vendor
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
if updated_count > 0:
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"已补全 {updated_count} 条厂商信息",
|
||||||
|
"checked": len(items),
|
||||||
|
"updated": updated_count
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/batch/hostname", summary="批量发现主机名")
|
||||||
|
def batch_discover_hostname(
|
||||||
|
network_id: Optional[int] = None,
|
||||||
|
enable_dns: bool = True,
|
||||||
|
enable_netbios: bool = True,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
对已有 MAC 地址但缺少主机名的 IP 批量执行主机名发现(DNS PTR + NetBIOS)
|
||||||
|
"""
|
||||||
|
from app.models.network import IPAddress as IPAddressModel
|
||||||
|
|
||||||
|
query = db.query(IPAddressModel).filter(
|
||||||
|
IPAddressModel.mac_address.isnot(None),
|
||||||
|
IPAddressModel.hostname.is_(None)
|
||||||
|
)
|
||||||
|
if network_id:
|
||||||
|
query = query.filter(IPAddressModel.network_id == network_id)
|
||||||
|
|
||||||
|
items = query.all()
|
||||||
|
updated_count = 0
|
||||||
|
|
||||||
|
for ip in items:
|
||||||
|
hostname = None
|
||||||
|
if enable_dns:
|
||||||
|
hostname = EnhancedScanService.reverse_dns_lookup(ip.ip_address, timeout=2)
|
||||||
|
if not hostname and enable_netbios:
|
||||||
|
hostname = EnhancedScanService.netbios_lookup(ip.ip_address, timeout=3)
|
||||||
|
if hostname:
|
||||||
|
ip.hostname = hostname
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
if updated_count > 0:
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"已发现 {updated_count} 条主机名",
|
||||||
|
"checked": len(items),
|
||||||
|
"updated": updated_count
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/online/ips", summary="获取所有在线IP列表")
|
@router.get("/online/ips", summary="获取所有在线IP列表")
|
||||||
def get_online_ips(
|
def get_online_ips(
|
||||||
|
|||||||
@@ -63,20 +63,29 @@ export const scanApi = {
|
|||||||
return api.post(`/scan/ping/${id}`)
|
return api.post(`/scan/ping/${id}`)
|
||||||
},
|
},
|
||||||
// 综合扫描网段
|
// 综合扫描网段
|
||||||
// options: { enable_ping, enable_arp, enable_dns, timeout }
|
// options: { enable_ping, enable_arp, enable_dns, enable_netbios, timeout }
|
||||||
// 单次调用覆盖全局 30s 超时,因为 /24 网段扫描要数分钟
|
// 单次调用覆盖全局 30s 超时,因为 /24 网段扫描要数分钟
|
||||||
comprehensiveScan(id, options = {}) {
|
comprehensiveScan(id, options = {}) {
|
||||||
const {
|
const {
|
||||||
enable_ping = true,
|
enable_ping = true,
|
||||||
enable_arp = true,
|
enable_arp = true,
|
||||||
enable_dns = true,
|
enable_dns = true,
|
||||||
|
enable_netbios = true,
|
||||||
timeout = 2
|
timeout = 2
|
||||||
} = options
|
} = options
|
||||||
return api.post(`/scan/comprehensive/${id}`, {}, {
|
return api.post(`/scan/comprehensive/${id}`, {}, {
|
||||||
params: { enable_ping, enable_arp, enable_dns, timeout },
|
params: { enable_ping, enable_arp, enable_dns, enable_netbios, timeout },
|
||||||
timeout: 600000 // 10 分钟,足以应付 /16 级别网段
|
timeout: 600000 // 10 分钟,足以应付 /16 级别网段
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 批量补全厂商信息
|
||||||
|
batchFillVendor(params) {
|
||||||
|
return api.post('/scan/batch/vendor', {}, { params })
|
||||||
|
},
|
||||||
|
// 批量发现主机名
|
||||||
|
batchDiscoverHostname(params) {
|
||||||
|
return api.post('/scan/batch/hostname', {}, { params })
|
||||||
|
},
|
||||||
// 单个IP扫描
|
// 单个IP扫描
|
||||||
scanIP(ipAddress) {
|
scanIP(ipAddress) {
|
||||||
return api.post(`/scan/comprehensive/ip/${ipAddress}`)
|
return api.post(`/scan/comprehensive/ip/${ipAddress}`)
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
<el-icon><Search /></el-icon>
|
<el-icon><Search /></el-icon>
|
||||||
批量扫描
|
批量扫描
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="warning" @click="batchFillVendor" :loading="vendorLoading">
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
补全厂商
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -176,6 +180,7 @@ const route = useRoute()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const scanning = ref(false)
|
const scanning = ref(false)
|
||||||
|
const vendorLoading = ref(false)
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
|
||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
@@ -278,6 +283,24 @@ const startBatchScan = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const batchFillVendor = async () => {
|
||||||
|
if (!filters.networkId) {
|
||||||
|
ElMessage.warning('请先选择网段')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vendorLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await scanApi.batchFillVendor({ network_id: filters.networkId })
|
||||||
|
ElMessage.success(res.message)
|
||||||
|
loadIPs()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('补全厂商失败')
|
||||||
|
console.error('补全厂商错误:', error)
|
||||||
|
} finally {
|
||||||
|
vendorLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const editIP = (row) => {
|
const editIP = (row) => {
|
||||||
ipForm.id = row.id
|
ipForm.id = row.id
|
||||||
ipForm.ip_address = row.ip_address
|
ipForm.ip_address = row.ip_address
|
||||||
|
|||||||
Reference in New Issue
Block a user