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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,120 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum, Boolean, Float
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class AlertType(str, enum.Enum):
|
||||
"""告警类型"""
|
||||
IP_CONFLICT = "ip_conflict" # IP 冲突
|
||||
UNAUTHORIZED_ACCESS = "unauthorized_access" # 未授权接入
|
||||
SUBNET_FULL = "subnet_full" # 网段耗尽
|
||||
SCAN_FAILED = "scan_failed" # 扫描失败
|
||||
DEVICE_OFFLINE = "device_offline" # 设备离线
|
||||
NEW_DEVICE_DETECTED = "new_device_detected" # 新设备发现
|
||||
MAC_CHANGED = "mac_changed" # MAC 地址变化
|
||||
|
||||
|
||||
class AlertSeverity(str, enum.Enum):
|
||||
"""告警级别"""
|
||||
INFO = "info" # 信息
|
||||
WARNING = "warning" # 警告
|
||||
ERROR = "error" # 错误
|
||||
CRITICAL = "critical" # 严重
|
||||
|
||||
|
||||
class AlertStatus(str, enum.Enum):
|
||||
"""告警状态"""
|
||||
ACTIVE = "active" # 活跃
|
||||
ACKNOWLEDGED = "acknowledged" # 已确认
|
||||
RESOLVED = "resolved" # 已解决
|
||||
IGNORED = "ignored" # 已忽略
|
||||
|
||||
|
||||
class Alert(Base):
|
||||
"""告警"""
|
||||
__tablename__ = "alerts"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
alert_type = Column(Enum(AlertType), nullable=False, index=True)
|
||||
severity = Column(Enum(AlertSeverity), default=AlertSeverity.WARNING, index=True)
|
||||
status = Column(Enum(AlertStatus), default=AlertStatus.ACTIVE, index=True)
|
||||
|
||||
title = Column(String(255), nullable=False)
|
||||
message = Column(Text)
|
||||
|
||||
# 关联对象
|
||||
network_id = Column(Integer, ForeignKey("networks.id"), nullable=True)
|
||||
ip_address_id = Column(Integer, ForeignKey("ip_addresses.id"), nullable=True)
|
||||
device_id = Column(Integer, ForeignKey("network_devices.id"), nullable=True)
|
||||
ip_address_str = Column(String(50), nullable=True) # 冗余存储便于查询
|
||||
|
||||
# 关联的 IP/MAC 信息
|
||||
mac_address = Column(String(50), nullable=True)
|
||||
conflicting_mac = Column(String(50), nullable=True) # 用于 IP 冲突告警
|
||||
|
||||
# 统计信息
|
||||
usage_percent = Column(Float, nullable=True) # 网段使用率告警时存储
|
||||
|
||||
# 处理信息
|
||||
acknowledged_by = Column(String(100), nullable=True)
|
||||
acknowledged_at = Column(DateTime(timezone=True), nullable=True)
|
||||
resolved_by = Column(String(100), nullable=True)
|
||||
resolved_at = Column(DateTime(timezone=True), nullable=True)
|
||||
resolution_notes = Column(Text, nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
network = relationship("Network")
|
||||
ip_address = relationship("IPAddress")
|
||||
device = relationship("NetworkDevice")
|
||||
|
||||
|
||||
class AlertRule(Base):
|
||||
"""告警规则配置"""
|
||||
__tablename__ = "alert_rules"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String(100), unique=True, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
alert_type = Column(Enum(AlertType), nullable=False)
|
||||
severity = Column(Enum(AlertSeverity), default=AlertSeverity.WARNING)
|
||||
|
||||
# 规则条件
|
||||
enabled = Column(Boolean, default=True)
|
||||
|
||||
# 阈值配置
|
||||
subnet_usage_threshold = Column(Float, default=90.0) # 网段使用率阈值 %
|
||||
scan_failure_threshold = Column(Integer, default=3) # 扫描失败次数阈值
|
||||
inactivity_threshold_minutes = Column(Integer, default=60) # 设备不活动阈值(分钟)
|
||||
|
||||
# MAC 白名单(用于未授权接入检测)
|
||||
mac_whitelist_enabled = Column(Boolean, default=False)
|
||||
mac_whitelist = Column(Text, nullable=True) # JSON 格式的 MAC 列表
|
||||
|
||||
# 通知配置
|
||||
notify_email = Column(Boolean, default=False)
|
||||
notify_webhook = Column(Boolean, default=False)
|
||||
notification_channels = Column(Text, nullable=True) # JSON 格式的通知渠道
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
|
||||
class WhitelistedMAC(Base):
|
||||
"""MAC 地址白名单"""
|
||||
__tablename__ = "whitelisted_macs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
mac_address = Column(String(50), unique=True, nullable=False, index=True)
|
||||
description = Column(String(255), nullable=True)
|
||||
owner = Column(String(100), nullable=True)
|
||||
|
||||
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())
|
||||
@@ -0,0 +1,37 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Index
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""审计日志:记录用户的关键操作"""
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# 操作人(nullable:登录失败、token 失效等场景可能没有 user_id)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
|
||||
username = Column(String(50), index=True, comment="冗余存储用户名,便于 user 被删除后仍能查看")
|
||||
|
||||
# 操作内容
|
||||
action = Column(String(50), nullable=False, index=True, comment="操作类型,如:network.create")
|
||||
resource_type = Column(String(50), nullable=True, index=True, comment="资源类型:network / ip / snmp / user / scan")
|
||||
resource_id = Column(String(50), nullable=True, comment="资源 ID(字符串以便兼容多种类型)")
|
||||
resource_name = Column(String(200), nullable=True, comment="资源名称/标识,便于阅读")
|
||||
|
||||
# 请求信息
|
||||
method = Column(String(10), comment="HTTP 方法")
|
||||
path = Column(String(500), comment="请求路径")
|
||||
ip_address = Column(String(50), comment="客户端 IP")
|
||||
user_agent = Column(String(500), comment="User-Agent")
|
||||
|
||||
# 状态
|
||||
status = Column(String(20), default="success", index=True, comment="success / failed")
|
||||
detail = Column(Text, comment="变更详情 / 错误信息 / JSON diff")
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
|
||||
|
||||
|
||||
# 复合索引:按时间+用户查、按时间+资源查更高效
|
||||
Index("idx_audit_logs_user_created", AuditLog.user_id, AuditLog.created_at.desc())
|
||||
Index("idx_audit_logs_resource", AuditLog.resource_type, AuditLog.resource_id, AuditLog.created_at.desc())
|
||||
@@ -0,0 +1,97 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Enum, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
import enum
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
"""用户角色枚举"""
|
||||
SUPER_ADMIN = "super_admin" # 超级管理员 - 所有权限
|
||||
ADMIN = "admin" # 管理员 - 除用户管理外的所有权限
|
||||
OPERATOR = "operator" # 操作员 - 可查看和操作,不能配置
|
||||
VIEWER = "viewer" # 只读用户 - 只能查看
|
||||
|
||||
|
||||
class UserStatus(str, enum.Enum):
|
||||
"""用户状态"""
|
||||
ACTIVE = "active" # 正常
|
||||
INACTIVE = "inactive" # 禁用
|
||||
LOCKED = "locked" # 锁定(登录失败过多)
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""用户表"""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||
email = Column(String(100), unique=True, index=True)
|
||||
phone = Column(String(20), index=True)
|
||||
real_name = Column(String(50), comment="真实姓名")
|
||||
|
||||
password_hash = Column(String(255), nullable=False)
|
||||
|
||||
role = Column(Enum(UserRole), default=UserRole.VIEWER, nullable=False)
|
||||
status = Column(Enum(UserStatus), default=UserStatus.ACTIVE, nullable=False)
|
||||
|
||||
# 登录信息
|
||||
last_login_at = Column(DateTime(timezone=True))
|
||||
last_login_ip = Column(String(50))
|
||||
login_failed_count = Column(Integer, default=0)
|
||||
|
||||
# 通知配置
|
||||
email_notification = Column(Boolean, default=True)
|
||||
wechat_notification = Column(Boolean, default=False)
|
||||
dingtalk_notification = Column(Boolean, default=False)
|
||||
|
||||
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
creator = relationship("User", remote_side=[id])
|
||||
|
||||
|
||||
class Permission(Base):
|
||||
"""权限项"""
|
||||
__tablename__ = "permissions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
code = Column(String(100), unique=True, nullable=False, comment="权限代码,如:network:create")
|
||||
name = Column(String(100), nullable=False, comment="权限名称")
|
||||
description = Column(Text)
|
||||
category = Column(String(50), comment="权限分类:network, ip, scan, snmp, alert, system")
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class RolePermission(Base):
|
||||
"""角色权限关联表"""
|
||||
__tablename__ = "role_permissions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
role = Column(Enum(UserRole), nullable=False)
|
||||
permission_id = Column(Integer, ForeignKey("permissions.id"), nullable=False)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class RefreshToken(Base):
|
||||
"""刷新令牌"""
|
||||
__tablename__ = "refresh_tokens"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
token = Column(String(500), unique=True, nullable=False)
|
||||
expires_at = Column(DateTime(timezone=True), nullable=False)
|
||||
ip_address = Column(String(50))
|
||||
user_agent = Column(String(500))
|
||||
|
||||
revoked = Column(Boolean, default=False)
|
||||
revoked_at = Column(DateTime(timezone=True))
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# 关系
|
||||
user = relationship("User")
|
||||
@@ -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")
|
||||
@@ -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