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
+119
View File
@@ -0,0 +1,119 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, Enum, ForeignKey, JSON, Boolean
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
import enum
class NetworkGroup(str, enum.Enum):
"""网段分组枚举"""
PRODUCTION = "production"
OFFICE = "office"
TEST = "test"
MANAGEMENT = "management"
OTHER = "other"
class IPStatus(str, enum.Enum):
"""IP状态枚举"""
AVAILABLE = "available"
ONLINE = "online"
OFFLINE = "offline"
RESERVED = "reserved"
class TaskStatus(str, enum.Enum):
"""任务状态枚举"""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
class TaskType(str, enum.Enum):
"""任务类型枚举"""
PING = "ping"
ARP = "arp"
SNMP = "snmp"
class Network(Base):
"""网段模型"""
__tablename__ = "networks"
id = Column(Integer, primary_key=True, index=True)
cidr = Column(String(50), unique=True, index=True, nullable=False) # 如: 192.168.1.0/24
name = Column(String(100), index=True)
description = Column(Text, nullable=True)
group_name = Column(String(100), index=True, nullable=True) # 分组名称
gateway = Column(String(50), nullable=True)
vlan_id = Column(Integer, nullable=True)
total_ips = Column(Integer, default=0)
used_ips = Column(Integer, default=0)
reserved_ips = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
ip_addresses = relationship("IPAddress", back_populates="network", cascade="all, delete-orphan")
scan_tasks = relationship("ScanTask", back_populates="network")
class IPAddress(Base):
"""IP地址模型"""
__tablename__ = "ip_addresses"
id = Column(Integer, primary_key=True, index=True)
network_id = Column(Integer, ForeignKey("networks.id"), nullable=False)
ip_address = Column(String(50), unique=True, index=True, nullable=False)
mac_address = Column(String(50), nullable=True, index=True)
hostname = Column(String(255), nullable=True)
status = Column(Enum(IPStatus), default=IPStatus.AVAILABLE)
# 业务属性
owner = Column(String(100), nullable=True)
business_type = Column(String(50), nullable=True)
notes = Column(Text, nullable=True)
custom_fields = Column(JSON, default=dict)
# 扫描信息
last_seen = Column(DateTime(timezone=True), nullable=True)
first_seen = Column(DateTime(timezone=True), server_default=func.now())
# 物理位置信息
switch_name = Column(String(100), nullable=True)
switch_port = Column(String(50), nullable=True)
vendor = Column(String(100), nullable=True) # MAC OUI 解析的厂商
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# 关系
network = relationship("Network", back_populates="ip_addresses")
class ScanTask(Base):
"""扫描任务模型"""
__tablename__ = "scan_tasks"
id = Column(Integer, primary_key=True, index=True)
network_id = Column(Integer, ForeignKey("networks.id"), nullable=True)
task_type = Column(Enum(TaskType), nullable=False)
status = Column(Enum(TaskStatus), default=TaskStatus.PENDING)
progress = Column(Integer, default=0) # 0-100
celery_task_id = Column(String(100), nullable=True)
# 统计信息
total_count = Column(Integer, default=0)
success_count = Column(Integer, default=0)
failed_count = Column(Integer, default=0)
started_at = Column(DateTime(timezone=True), nullable=True)
completed_at = Column(DateTime(timezone=True), nullable=True)
error_message = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# 关系
network = relationship("Network", back_populates="scan_tasks")