fix(ipam): 修复IP管理页面网段筛选不生效及下拉框过窄
- Ips.vue: el-select 添加 width: 280px,修复网段下拉框过窄 - Ips.vue: query 参数 networkId → network_id(与 FastAPI snake_case 一致) - Scan.vue: el-select 用 network.id 代替整个 network 对象作为 value,修复切换网段不生效 - enhanced_scan_service.py: 替换失效的 scapy ARP 扫描为 arp-scan 命令;扫描结果自动创建缺失 IP 记录
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
from typing import List, Dict, Any, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
import subprocess
|
||||
import ipaddress
|
||||
|
||||
from app.models.network import ScanTask, Network, IPAddress
|
||||
from app.models.network import TaskStatus, TaskType, IPStatus
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
class ScanService:
|
||||
"""扫描服务"""
|
||||
|
||||
@staticmethod
|
||||
def ping_single_ip(ip_address: str, timeout: int = 2) -> Dict[str, Any]:
|
||||
"""
|
||||
对单个IP执行Ping扫描
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['ping', '-c', '1', '-W', str(timeout), ip_address],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
is_alive = result.returncode == 0
|
||||
|
||||
return {
|
||||
'ip_address': ip_address,
|
||||
'status': 'online' if is_alive else 'offline',
|
||||
'response_time': None,
|
||||
'success': is_alive
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'ip_address': ip_address,
|
||||
'status': 'error',
|
||||
'error': str(e),
|
||||
'success': False
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def ping_network(cidr: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
对整个网段执行Ping扫描(同步版本)
|
||||
"""
|
||||
network = ipaddress.ip_network(cidr, strict=False)
|
||||
ips = [str(ip) for ip in network.hosts()]
|
||||
|
||||
results = []
|
||||
for ip in ips:
|
||||
result = ScanService.ping_single_ip(ip, settings.PING_TIMEOUT)
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def create_scan_task(db: Session, task_type: TaskType,
|
||||
network_id: Optional[int] = None) -> ScanTask:
|
||||
"""
|
||||
创建扫描任务
|
||||
"""
|
||||
task = ScanTask(
|
||||
task_type=task_type,
|
||||
network_id=network_id,
|
||||
status=TaskStatus.PENDING,
|
||||
progress=0
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def update_task_status(db: Session, task_id: int, status: TaskStatus,
|
||||
progress: int = None, total_count: int = None,
|
||||
success_count: int = None, failed_count: int = None,
|
||||
error_message: str = None):
|
||||
"""
|
||||
更新任务状态
|
||||
"""
|
||||
task = db.query(ScanTask).filter(ScanTask.id == task_id).first()
|
||||
if not task:
|
||||
return
|
||||
|
||||
task.status = status
|
||||
if progress is not None:
|
||||
task.progress = progress
|
||||
if total_count is not None:
|
||||
task.total_count = total_count
|
||||
if success_count is not None:
|
||||
task.success_count = success_count
|
||||
if failed_count is not None:
|
||||
task.failed_count = failed_count
|
||||
if error_message:
|
||||
task.error_message = error_message
|
||||
|
||||
if status == TaskStatus.RUNNING and task.started_at is None:
|
||||
task.started_at = datetime.utcnow()
|
||||
elif status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
|
||||
task.completed_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
@staticmethod
|
||||
def get_task_by_id(db: Session, task_id: int) -> Optional[ScanTask]:
|
||||
"""
|
||||
获取任务信息
|
||||
"""
|
||||
return db.query(ScanTask).filter(ScanTask.id == task_id).first()
|
||||
|
||||
@staticmethod
|
||||
def get_task_list(db: Session, skip: int = 0, limit: int = 50) -> tuple[int, List[ScanTask]]:
|
||||
"""
|
||||
获取任务列表
|
||||
"""
|
||||
query = db.query(ScanTask)
|
||||
total = query.count()
|
||||
items = query.order_by(ScanTask.id.desc()).offset(skip).limit(limit).all()
|
||||
return total, items
|
||||
|
||||
@staticmethod
|
||||
def update_ip_status_from_scan_results(db: Session, results: List[Dict[str, Any]]):
|
||||
"""
|
||||
根据扫描结果更新IP状态
|
||||
"""
|
||||
online_count = 0
|
||||
offline_count = 0
|
||||
|
||||
for result in results:
|
||||
ip_address = result.get('ip_address')
|
||||
is_online = result.get('success', False)
|
||||
|
||||
db_ip = db.query(IPAddress).filter(IPAddress.ip_address == ip_address).first()
|
||||
if db_ip:
|
||||
if is_online:
|
||||
db_ip.status = IPStatus.ONLINE
|
||||
db_ip.last_seen = datetime.utcnow()
|
||||
online_count += 1
|
||||
elif db_ip.status == IPStatus.ONLINE:
|
||||
# 曾经在线,现在不在线,标记为离线
|
||||
db_ip.status = IPStatus.OFFLINE
|
||||
offline_count += 1
|
||||
|
||||
db.commit()
|
||||
return online_count, offline_count
|
||||
Reference in New Issue
Block a user