Files
ipam/backend/app/models/network.py
T
Your Name 1e1e5957e8 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 记录
2026-07-18 09:45:30 +08:00

120 lines
3.9 KiB
Python

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")