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,173 @@
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
import ipaddress
|
||||
from app.models.network import IPStatus, TaskStatus, TaskType
|
||||
|
||||
|
||||
# ========== 网段相关 Schemas ==========
|
||||
|
||||
class NetworkBase(BaseModel):
|
||||
cidr: str = Field(..., description="网段CIDR,如: 192.168.1.0/24")
|
||||
name: Optional[str] = Field(None, max_length=100, description="网段名称")
|
||||
description: Optional[str] = Field(None, description="描述")
|
||||
group_name: Optional[str] = Field(None, max_length=100, description="分组名称")
|
||||
gateway: Optional[str] = Field(None, max_length=50, description="网关地址")
|
||||
vlan_id: Optional[int] = Field(None, description="VLAN ID")
|
||||
|
||||
@field_validator('cidr')
|
||||
@classmethod
|
||||
def validate_cidr(cls, v):
|
||||
try:
|
||||
network = ipaddress.ip_network(v, strict=False)
|
||||
return str(network)
|
||||
except ValueError:
|
||||
raise ValueError(f"无效的CIDR格式: {v}")
|
||||
|
||||
@field_validator('gateway')
|
||||
@classmethod
|
||||
def validate_gateway(cls, v):
|
||||
if v:
|
||||
try:
|
||||
ipaddress.ip_address(v)
|
||||
except ValueError:
|
||||
raise ValueError(f"无效的IP地址: {v}")
|
||||
return v
|
||||
|
||||
|
||||
class NetworkCreate(NetworkBase):
|
||||
pass
|
||||
|
||||
|
||||
class NetworkUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
group_name: Optional[str] = None
|
||||
gateway: Optional[str] = None
|
||||
vlan_id: Optional[int] = None
|
||||
|
||||
|
||||
class Network(NetworkBase):
|
||||
id: int
|
||||
total_ips: int
|
||||
used_ips: int
|
||||
reserved_ips: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NetworkStats(BaseModel):
|
||||
"""网段统计信息"""
|
||||
id: int
|
||||
cidr: str
|
||||
name: Optional[str]
|
||||
total_ips: int
|
||||
used_ips: int
|
||||
reserved_ips: int
|
||||
available_ips: int
|
||||
usage_percent: float
|
||||
group_name: Optional[str]
|
||||
vlan_id: Optional[int]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NetworkListResponse(BaseModel):
|
||||
total: int
|
||||
items: List[Network]
|
||||
|
||||
|
||||
# ========== IP地址相关 Schemas ==========
|
||||
|
||||
class IPAddressBase(BaseModel):
|
||||
ip_address: str
|
||||
mac_address: Optional[str] = None
|
||||
hostname: Optional[str] = None
|
||||
owner: Optional[str] = None
|
||||
business_type: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Dict[str, Any] = Field(default_factory=dict)
|
||||
switch_name: Optional[str] = None
|
||||
switch_port: Optional[str] = None
|
||||
vendor: Optional[str] = None
|
||||
|
||||
|
||||
class IPAddressCreate(IPAddressBase):
|
||||
network_id: int
|
||||
status: IPStatus = IPStatus.AVAILABLE
|
||||
|
||||
|
||||
class IPAddressUpdate(BaseModel):
|
||||
mac_address: Optional[str] = None
|
||||
hostname: Optional[str] = None
|
||||
status: Optional[IPStatus] = None
|
||||
owner: Optional[str] = None
|
||||
business_type: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
custom_fields: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class IPAddress(IPAddressBase):
|
||||
id: int
|
||||
network_id: int
|
||||
status: IPStatus
|
||||
last_seen: Optional[datetime]
|
||||
first_seen: datetime
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class IPAddressListResponse(BaseModel):
|
||||
total: int
|
||||
items: List[IPAddress]
|
||||
|
||||
|
||||
# ========== 扫描任务相关 Schemas ==========
|
||||
|
||||
class ScanTaskBase(BaseModel):
|
||||
task_type: TaskType
|
||||
network_id: Optional[int] = None
|
||||
|
||||
|
||||
class ScanTaskCreate(ScanTaskBase):
|
||||
pass
|
||||
|
||||
|
||||
class ScanTask(BaseModel):
|
||||
id: int
|
||||
network_id: Optional[int]
|
||||
task_type: TaskType
|
||||
status: TaskStatus
|
||||
progress: int
|
||||
celery_task_id: Optional[str]
|
||||
total_count: int
|
||||
success_count: int
|
||||
failed_count: int
|
||||
started_at: Optional[datetime]
|
||||
completed_at: Optional[datetime]
|
||||
error_message: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ScanTaskListResponse(BaseModel):
|
||||
total: int
|
||||
items: List[ScanTask]
|
||||
|
||||
|
||||
class ScanResult(BaseModel):
|
||||
"""单次扫描结果"""
|
||||
ip_address: str
|
||||
status: str
|
||||
mac_address: Optional[str] = None
|
||||
hostname: Optional[str] = None
|
||||
response_time: Optional[float] = None
|
||||
Reference in New Issue
Block a user