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 记录
37 lines
1.8 KiB
Python
37 lines
1.8 KiB
Python
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()) |