1e1e5957e8
- 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 记录
191 lines
6.7 KiB
Python
191 lines
6.7 KiB
Python
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")
|