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