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,190 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class SNMPVersion(str, enum.Enum):
|
||||
"""SNMP 版本"""
|
||||
V2C = "v2c"
|
||||
V3 = "v3"
|
||||
|
||||
|
||||
class SNMPAuthProtocol(str, enum.Enum):
|
||||
"""SNMP V3 认证协议"""
|
||||
MD5 = "md5"
|
||||
SHA = "sha"
|
||||
SHA256 = "sha256"
|
||||
|
||||
|
||||
class SNMPPrivProtocol(str, enum.Enum):
|
||||
"""SNMP V3 加密协议"""
|
||||
DES = "des"
|
||||
AES = "aes"
|
||||
AES256 = "aes256"
|
||||
|
||||
|
||||
class DeviceType(str, enum.Enum):
|
||||
"""设备类型"""
|
||||
ROUTER = "router"
|
||||
CORE_SWITCH = "core_switch"
|
||||
DISTRIBUTION_SWITCH = "distribution_switch"
|
||||
ACCESS_SWITCH = "access_switch"
|
||||
FIREWALL = "firewall"
|
||||
LOAD_BALANCER = "load_balancer"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class SNMPCredential(Base):
|
||||
"""SNMP 凭据"""
|
||||
__tablename__ = "snmp_credentials"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String(100), unique=True, index=True, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# 版本
|
||||
version = Column(Enum(SNMPVersion), default=SNMPVersion.V2C, nullable=False)
|
||||
|
||||
# V2C 配置
|
||||
community_string = Column(String(255), nullable=True)
|
||||
|
||||
# V3 配置
|
||||
username = Column(String(100), nullable=True)
|
||||
auth_password = Column(String(255), nullable=True)
|
||||
auth_protocol = Column(Enum(SNMPAuthProtocol), nullable=True)
|
||||
priv_password = Column(String(255), nullable=True)
|
||||
priv_protocol = Column(Enum(SNMPPrivProtocol), nullable=True)
|
||||
|
||||
# 超时和重试
|
||||
timeout = Column(Integer, default=3)
|
||||
retries = Column(Integer, default=2)
|
||||
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
devices = relationship("NetworkDevice", back_populates="snmp_credential")
|
||||
|
||||
|
||||
class NetworkDevice(Base):
|
||||
"""网络设备"""
|
||||
__tablename__ = "network_devices"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String(100), unique=True, index=True, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
# 连接信息
|
||||
ip_address = Column(String(50), index=True, nullable=False)
|
||||
port = Column(Integer, default=161)
|
||||
|
||||
# 设备信息
|
||||
device_type = Column(Enum(DeviceType), default=DeviceType.OTHER)
|
||||
vendor = Column(String(100), nullable=True)
|
||||
model = Column(String(100), nullable=True)
|
||||
firmware_version = Column(String(100), nullable=True)
|
||||
serial_number = Column(String(100), nullable=True)
|
||||
location = Column(String(200), nullable=True)
|
||||
|
||||
# SNMP 凭据
|
||||
snmp_credential_id = Column(Integer, ForeignKey("snmp_credentials.id"), nullable=True)
|
||||
|
||||
# 状态
|
||||
is_active = Column(Boolean, default=True)
|
||||
last_polled_at = Column(DateTime(timezone=True), nullable=True)
|
||||
last_successful_poll = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# 采集配置
|
||||
arp_poll_interval = Column(Integer, default=300) # ARP表采集间隔(秒)
|
||||
mac_poll_interval = Column(Integer, default=300) # MAC表采集间隔(秒)
|
||||
interface_poll_interval = Column(Integer, default=300) # 接口信息采集间隔
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
snmp_credential = relationship("SNMPCredential", back_populates="devices")
|
||||
arp_entries = relationship("ARPEntry", back_populates="device", cascade="all, delete-orphan")
|
||||
mac_entries = relationship("MACAddressTable", back_populates="device", cascade="all, delete-orphan")
|
||||
interfaces = relationship("DeviceInterface", back_populates="device", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class ARPEntry(Base):
|
||||
"""ARP 表条目 (IP -> MAC 映射)"""
|
||||
__tablename__ = "arp_entries"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
device_id = Column(Integer, ForeignKey("network_devices.id"), nullable=False)
|
||||
|
||||
ip_address = Column(String(50), index=True, nullable=False)
|
||||
mac_address = Column(String(50), index=True, nullable=False)
|
||||
interface = Column(String(100), nullable=True) # 接口名称/索引
|
||||
vlan_id = Column(Integer, nullable=True)
|
||||
|
||||
# 老化信息
|
||||
age = Column(Integer, nullable=True) # 秒
|
||||
is_static = Column(Boolean, default=False)
|
||||
|
||||
discovered_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
last_seen = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 关系
|
||||
device = relationship("NetworkDevice", back_populates="arp_entries")
|
||||
|
||||
|
||||
class MACAddressTable(Base):
|
||||
"""MAC 地址表 (MAC -> 端口 映射)"""
|
||||
__tablename__ = "mac_address_table"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
device_id = Column(Integer, ForeignKey("network_devices.id"), nullable=False)
|
||||
|
||||
mac_address = Column(String(50), index=True, nullable=False)
|
||||
vlan_id = Column(Integer, nullable=True)
|
||||
interface = Column(String(100), nullable=True) # 接口名称
|
||||
port_number = Column(Integer, nullable=True) # 端口号
|
||||
|
||||
# 类型: learned, static, management, etc.
|
||||
entry_type = Column(String(50), nullable=True)
|
||||
|
||||
discovered_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
last_seen = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 关系
|
||||
device = relationship("NetworkDevice", back_populates="mac_entries")
|
||||
|
||||
|
||||
class DeviceInterface(Base):
|
||||
"""设备接口信息"""
|
||||
__tablename__ = "device_interfaces"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
device_id = Column(Integer, ForeignKey("network_devices.id"), nullable=False)
|
||||
|
||||
if_index = Column(Integer, nullable=False) # ifIndex
|
||||
if_name = Column(String(100), nullable=True) # ifName
|
||||
if_descr = Column(String(255), nullable=True) # ifDescr
|
||||
if_type = Column(String(50), nullable=True) # ifType
|
||||
if_mtu = Column(Integer, nullable=True) # ifMtu
|
||||
if_speed = Column(Integer, nullable=True) # ifSpeed (bps)
|
||||
if_phys_address = Column(String(50), nullable=True) # ifPhysAddress (MAC)
|
||||
if_admin_status = Column(String(50), nullable=True) # up/down/testing
|
||||
if_oper_status = Column(String(50), nullable=True) # up/down/testing
|
||||
|
||||
# IP 地址
|
||||
ip_address = Column(String(50), nullable=True)
|
||||
ip_netmask = Column(String(50), nullable=True)
|
||||
|
||||
# 统计
|
||||
if_in_octets = Column(Integer, nullable=True)
|
||||
if_out_octets = Column(Integer, nullable=True)
|
||||
if_in_errors = Column(Integer, nullable=True)
|
||||
if_out_errors = Column(Integer, nullable=True)
|
||||
|
||||
last_polled_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# 关系
|
||||
device = relationship("NetworkDevice", back_populates="interfaces")
|
||||
Reference in New Issue
Block a user