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:
Your Name
2026-07-18 09:45:09 +08:00
commit 1e1e5957e8
103 changed files with 13196 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List, Dict, Any, Optional
from app.core.database import get_db
from app.core.security import get_current_user
from app.schemas.network import ScanTask
from app.models.network import TaskStatus
from app.services.network_service import NetworkService
from app.services.scan_service import ScanService
router = APIRouter(
prefix="/async-scan",
tags=["异步扫描"],
dependencies=[Depends(get_current_user)],
)
@router.post("/network/{network_id}", response_model=ScanTask, summary="异步扫描网段")
def async_scan_network(
network_id: int,
enable_ping: bool = True,
enable_arp: bool = True,
enable_dns: bool = True,
timeout: int = 2,
db: Session = Depends(get_db)
):
"""
提交异步网段扫描任务,立即返回任务ID,后台执行
"""
network = NetworkService.get_by_id(db, network_id)
if not network:
raise HTTPException(status_code=404, detail="网段不存在")
# 创建数据库任务记录
db_task = ScanService.create_scan_task(db, 'ping', network_id)
# 提交 Celery 任务
from app.tasks.celery_app import scan_network_task
celery_task = scan_network_task.delay(
network_id=network_id,
enable_ping=enable_ping,
enable_arp=enable_arp,
enable_dns=enable_dns,
timeout=timeout
)
# 记录 Celery 任务ID
db_task.celery_task_id = celery_task.id
db.commit()
db.refresh(db_task)
return db_task
@router.post("/ip/{ip_address}", summary="异步扫描单个IP")
def async_scan_ip(
ip_address: str,
enable_ping: bool = True,
enable_arp: bool = True,
enable_dns: bool = True
):
"""
提交异步单个IP扫描任务
"""
from app.tasks.celery_app import scan_single_ip_task
celery_task = scan_single_ip_task.delay(
ip_address=ip_address,
enable_ping=enable_ping,
enable_arp=enable_arp,
enable_dns=enable_dns
)
return {
"task_id": celery_task.id,
"ip_address": ip_address,
"status": "pending"
}
@router.post("/quick", summary="快速批量IP状态更新")
def quick_scan_ips(ip_addresses: List[str]):
"""
快速批量更新IP状态(只做Ping+ARP,不做DNS
"""
from app.tasks.celery_app import quick_status_update
celery_task = quick_status_update.delay(ip_addresses=ip_addresses)
return {
"task_id": celery_task.id,
"ips_count": len(ip_addresses),
"status": "pending"
}
@router.get("/task/{task_id}", summary="查询异步任务状态")
def get_async_task_status(task_id: str, db: Session = Depends(get_db)):
"""
根据 Celery 任务ID 查询任务状态
"""
# 先查询数据库任务
from app.models.network import ScanTask as DbScanTask
db_task = db.query(DbScanTask).filter(DbScanTask.celery_task_id == task_id).first()
# 直接查询 Celery
from celery.result import AsyncResult
result = AsyncResult(task_id)
response = {
"task_id": task_id,
"celery_status": result.status
}
if db_task:
response["db_status"] = db_task.status.value
response["progress"] = db_task.progress
response["total_count"] = db_task.total_count
response["success_count"] = db_task.success_count
response["started_at"] = db_task.started_at
response["completed_at"] = db_task.completed_at
if result.status == 'SUCCESS':
response["result"] = result.result
elif result.status == 'FAILURE':
response["error"] = str(result.info)
return response
@router.get("/tasks/running", summary="获取运行中的任务列表")
def get_running_tasks(db: Session = Depends(get_db)):
"""
获取所有正在运行的扫描任务
"""
from app.models.network import ScanTask as DbScanTask
running = db.query(DbScanTask).filter(DbScanTask.status == TaskStatus.RUNNING).all()
pending = db.query(DbScanTask).filter(DbScanTask.status == TaskStatus.PENDING).all()
return {
"running_count": len(running),
"pending_count": len(pending),
"running_tasks": running,
"pending_tasks": pending
}
@router.post("/task/{task_id}/cancel", summary="取消异步任务")
def cancel_async_task(task_id: str):
"""
取消正在运行的异步任务
"""
from celery.result import AsyncResult
result = AsyncResult(task_id)
if result.status in ['PENDING', 'RUNNING']:
result.revoke(terminate=True)
return {"status": "cancelled", "task_id": task_id}
else:
return {"status": "not_running", "current_status": result.status}
@router.post("/full-scan", summary="触发全量扫描")
def trigger_full_scan(
enable_ping: bool = True,
enable_arp: bool = True,
enable_dns: bool = True
):
"""
触发全量网段扫描(所有网段)
"""
from app.tasks.celery_app import full_network_scan
celery_task = full_network_scan.delay(
enable_ping=enable_ping,
enable_arp=enable_arp,
enable_dns=enable_dns
)
return {
"task_id": celery_task.id,
"status": "pending",
"message": "全量扫描任务已提交"
}
@router.get("/celery/inspect", summary="Celery Worker 状态检查")
def inspect_celery():
"""
检查 Celery Worker 状态
"""
from app.tasks.celery_app import celery_app
try:
inspector = celery_app.control.inspect()
active = inspector.active()
scheduled = inspector.scheduled()
reserved = inspector.reserved()
stats = inspector.stats()
return {
"status": "healthy" if stats else "no_workers",
"active_tasks": active,
"scheduled_tasks": scheduled,
"reserved_tasks": reserved,
"worker_stats": stats
}
except Exception as e:
return {
"status": "error",
"error": str(e),
"message": "Celery Worker 可能未启动"
}