mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-10-14 01:39:18 +00:00
299 lines
9.4 KiB
Python
299 lines
9.4 KiB
Python
import socket
|
|
from fastapi import (APIRouter, HTTPException, Response)
|
|
from typing import List, Optional
|
|
from pydantic import BaseModel
|
|
import asyncio
|
|
import psutil
|
|
import ipaddress
|
|
import time
|
|
from datetime import datetime
|
|
|
|
from ..models.requests import CLICommandRequest, ConfigRequest, TrafficQueryRequest
|
|
from ...app.services.ai_services import AIService
|
|
from ...app.api.network_config import SwitchConfigurator
|
|
from ...config import settings
|
|
from ..services.network_scanner import NetworkScanner
|
|
from ..services.traffic_monitor import traffic_monitor
|
|
from ..utils.logger import logger
|
|
|
|
router = APIRouter(prefix="", tags=["API"])
|
|
scanner = NetworkScanner()
|
|
|
|
|
|
@router.get("/", include_in_schema=False)
|
|
async def root():
|
|
return {
|
|
"message": "欢迎使用AI交换机配置系统",
|
|
"docs": f"{settings.API_PREFIX}/docs",
|
|
"redoc": f"{settings.API_PREFIX}/redoc",
|
|
"endpoints": [
|
|
"/parse_command",
|
|
"/apply_config",
|
|
"/scan_network",
|
|
"/list_devices",
|
|
"/batch_apply_config",
|
|
"/traffic/realtime",
|
|
"/traffic/cache/clear"
|
|
]
|
|
}
|
|
|
|
|
|
@router.get("/favicon.ico", include_in_schema=False)
|
|
async def favicon():
|
|
return Response(status_code=204)
|
|
|
|
|
|
class BatchConfigRequest(BaseModel):
|
|
config: dict
|
|
switch_ips: List[str]
|
|
username: str = None
|
|
password: str = None
|
|
timeout: int = None
|
|
|
|
@router.get("/test")
|
|
async def test_endpoint():
|
|
return {"message": "Hello World"}
|
|
|
|
|
|
@router.get("/scan_network", summary="扫描网络中的交换机")
|
|
async def scan_network(subnet: str = "192.168.1.0/24"):
|
|
try:
|
|
devices = await asyncio.to_thread(scanner.scan_subnet, subnet)
|
|
return {
|
|
"success": True,
|
|
"devices": devices,
|
|
"count": len(devices)
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(500, f"扫描失败: {str(e)}")
|
|
|
|
|
|
@router.get("/list_devices", summary="列出已发现的交换机")
|
|
async def list_devices():
|
|
return {
|
|
"devices": await asyncio.to_thread(scanner.load_cached_devices)
|
|
}
|
|
|
|
|
|
class DeviceItem(BaseModel):
|
|
name: str
|
|
ip: str
|
|
vendor: str
|
|
|
|
class CommandRequest(BaseModel):
|
|
command: str
|
|
devices: List[DeviceItem]
|
|
|
|
@router.post("/parse_command", response_model=dict)
|
|
async def parse_command(request: CommandRequest):
|
|
"""解析中文命令并返回每台设备的配置 JSON"""
|
|
missing_vendor = [d for d in request.devices if not d.vendor or d.vendor.strip() == ""]
|
|
if missing_vendor:
|
|
names = ", ".join([d.name for d in missing_vendor])
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"以下设备未配置厂商: {names}"
|
|
)
|
|
try:
|
|
ai_service = AIService(settings.SILICONFLOW_API_KEY, settings.SILICONFLOW_API_URL)
|
|
config = await ai_service.parse_command(request.command, [d.dict() for d in request.devices])
|
|
return {"success": True, "config": config.get("results", [])}
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Failed to parse command: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.post("/apply_config", response_model=dict)
|
|
async def apply_config(request: ConfigRequest):
|
|
"""应用配置到交换机"""
|
|
try:
|
|
configurator = SwitchConfigurator(
|
|
username=request.username,
|
|
password=request.password,
|
|
timeout=request.timeout,
|
|
vendor=request.vendor
|
|
)
|
|
result = await configurator.safe_apply(request.switch_ip, request.config)
|
|
return {"success": True, "result": result}
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Failed to apply config: {str(e)}"
|
|
)
|
|
@router.post("/execute_cli_commands", response_model=dict)
|
|
async def execute_cli_commands(request: CLICommandRequest):
|
|
"""执行前端生成的CLI命令"""
|
|
try:
|
|
username, password = request.extract_credentials()
|
|
|
|
configurator = SwitchConfigurator(
|
|
username=username,
|
|
password=password,
|
|
timeout=settings.SWITCH_TIMEOUT,
|
|
)
|
|
|
|
result = await configurator.execute_raw_commands(
|
|
ip=request.switch_ip,
|
|
commands=request.commands
|
|
)
|
|
return {
|
|
"success": True,
|
|
"output": result,
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(500, detail=str(e))
|
|
|
|
@router.get("/network_adapters", summary="获取网络适配器网段")
|
|
async def get_network_adapters():
|
|
try:
|
|
def sync_get_adapters():
|
|
net_if_addrs = psutil.net_if_addrs()
|
|
networks = []
|
|
for interface, addrs in net_if_addrs.items():
|
|
for addr in addrs:
|
|
if addr.family == socket.AF_INET:
|
|
ip = addr.address
|
|
netmask = addr.netmask
|
|
network = ipaddress.IPv4Network(f"{ip}/{netmask}", strict=False)
|
|
networks.append({
|
|
"adapter": interface,
|
|
"network": str(network),
|
|
"ip": ip,
|
|
"subnet_mask": netmask
|
|
})
|
|
return networks
|
|
|
|
networks = await asyncio.to_thread(sync_get_adapters)
|
|
return {"networks": networks}
|
|
except Exception as e:
|
|
return {"error": f"获取网络适配器信息失败: {str(e)}"}
|
|
|
|
|
|
@router.post("/traffic/realtime", summary="查询交换机接口实时流量")
|
|
async def get_realtime_traffic(request: TrafficQueryRequest):
|
|
"""
|
|
查询交换机接口实时流量速率(Kbps)
|
|
|
|
- 支持多个接口同时查询
|
|
- 首次查询速率返回 0
|
|
- 单个接口查询失败不影响其他接口
|
|
"""
|
|
# 提取认证信息
|
|
username = request.username or settings.SWITCH_USERNAME
|
|
password = request.password or settings.SWITCH_PASSWORD
|
|
|
|
# 创建配置器(复用连接池)
|
|
configurator = SwitchConfigurator(
|
|
username=username,
|
|
password=password,
|
|
timeout=settings.SWITCH_TIMEOUT
|
|
)
|
|
|
|
results = []
|
|
current_time = time.time()
|
|
|
|
# 遍历所有接口
|
|
for interface in request.interfaces:
|
|
interface_data = {
|
|
"interface": interface,
|
|
"status": "unknown",
|
|
"in_speed_kbps": 0.0,
|
|
"out_speed_kbps": 0.0,
|
|
"in_bytes": 0,
|
|
"out_bytes": 0,
|
|
"error": None
|
|
}
|
|
|
|
try:
|
|
# 获取查询命令
|
|
command = traffic_monitor.get_query_command(request.vendor, interface)
|
|
if not command:
|
|
interface_data["error"] = f"不支持的厂商: {request.vendor}"
|
|
results.append(interface_data)
|
|
continue
|
|
|
|
# 执行查询命令
|
|
try:
|
|
output = await configurator.execute_raw_commands(
|
|
ip=request.switch_ip,
|
|
commands=[command]
|
|
)
|
|
|
|
# 解析输出
|
|
stats = traffic_monitor.parse_interface_stats(request.vendor, str(output))
|
|
if stats is None:
|
|
interface_data["error"] = "解析接口统计失败"
|
|
results.append(interface_data)
|
|
continue
|
|
|
|
in_bytes, out_bytes, status = stats
|
|
|
|
# 计算速率
|
|
in_speed_kbps, out_speed_kbps = traffic_monitor.calculate_speed(
|
|
request.switch_ip,
|
|
interface,
|
|
in_bytes,
|
|
out_bytes,
|
|
current_time
|
|
)
|
|
|
|
# 更新结果
|
|
interface_data.update({
|
|
"status": status,
|
|
"in_speed_kbps": round(in_speed_kbps, 2),
|
|
"out_speed_kbps": round(out_speed_kbps, 2),
|
|
"in_bytes": in_bytes,
|
|
"out_bytes": out_bytes
|
|
})
|
|
|
|
except Exception as e:
|
|
interface_data["error"] = f"查询失败: {str(e)}"
|
|
logger.error(f"查询接口 {interface} 失败: {e}")
|
|
|
|
except Exception as e:
|
|
interface_data["error"] = f"未知错误: {str(e)}"
|
|
logger.error(f"处理接口 {interface} 时发生异常: {e}", exc_info=True)
|
|
|
|
results.append(interface_data)
|
|
|
|
return {
|
|
"success": True,
|
|
"switch_ip": request.switch_ip,
|
|
"vendor": request.vendor,
|
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
"data": results
|
|
}
|
|
|
|
|
|
@router.delete("/traffic/cache/clear", summary="清除流量监控缓存")
|
|
async def clear_traffic_cache(switch_ip: Optional[str] = None):
|
|
"""
|
|
清除流量监控缓存
|
|
|
|
- 不指定 switch_ip: 清除所有缓存
|
|
- 指定 switch_ip: 只清除该交换机的缓存
|
|
"""
|
|
try:
|
|
count = traffic_monitor.clear_cache(switch_ip)
|
|
return {
|
|
"success": True,
|
|
"message": f"已清除 {count} 条缓存记录",
|
|
"switch_ip": switch_ip or "all"
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(500, f"清除缓存失败: {str(e)}")
|
|
|
|
|
|
@router.get("/traffic/cache/stats", summary="获取缓存统计信息")
|
|
async def get_cache_stats():
|
|
"""获取流量监控缓存统计信息"""
|
|
try:
|
|
stats = traffic_monitor.get_cache_stats()
|
|
return {
|
|
"success": True,
|
|
"stats": stats
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(500, f"获取缓存统计失败: {str(e)}") |