This commit is contained in:
3 2025-07-11 13:25:44 +08:00
parent a57e4d4b46
commit 7030adb81c
4 changed files with 50 additions and 14 deletions

View File

@ -50,13 +50,19 @@ async def favicon():
class BatchConfigRequest(BaseModel): class BatchConfigRequest(BaseModel):
config: dict config: dict
switch_ips: List[str] # 支持多个IP switch_ips: List[str] # 支持多个IP
username: str = None # 添加用户名参数
password: str = None # 添加密码参数
timeout: int = None
@router.post("/batch_apply_config") @router.post("/batch_apply_config")
async def batch_apply_config(request: BatchConfigRequest): async def batch_apply_config(request: BatchConfigRequest):
results = {} results = {}
for ip in request.switch_ips: for ip in request.switch_ips:
try: try:
configurator = SwitchConfigurator() configurator = SwitchConfigurator(
username=request.username,
password=request.password,
timeout=request.timeout )
results[ip] = await configurator.apply_config(ip, request.config) results[ip] = await configurator.apply_config(ip, request.config)
except Exception as e: except Exception as e:
results[ip] = str(e) results[ip] = str(e)
@ -92,6 +98,9 @@ class CommandRequest(BaseModel):
class ConfigRequest(BaseModel): class ConfigRequest(BaseModel):
config: dict config: dict
switch_ip: str switch_ip: str
username: str = None
password: str = None
timeout: int = None
@router.post("/parse_command", response_model=dict) @router.post("/parse_command", response_model=dict)
async def parse_command(request: CommandRequest): async def parse_command(request: CommandRequest):
@ -115,9 +124,9 @@ async def apply_config(request: ConfigRequest):
""" """
try: try:
configurator = SwitchConfigurator( configurator = SwitchConfigurator(
username=settings.SWITCH_USERNAME, username=request.username,
password=settings.SWITCH_PASSWORD, password=request.password,
timeout=settings.SWITCH_TIMEOUT timeout=request.timeout
) )
result = await configurator.safe_apply(request.switch_ip, request.config) result = await configurator.safe_apply(request.switch_ip, request.config)
return {"success": True, "result": result} return {"success": True, "result": result}
@ -133,16 +142,40 @@ class CLICommandRequest(BaseModel):
commands: List[str] # 前端生成的CLI命令列表 commands: List[str] # 前端生成的CLI命令列表
is_ensp: bool = False # 是否为eNSP模拟器模式 is_ensp: bool = False # 是否为eNSP模拟器模式
# 添加方法从commands中提取凭据
def extract_credentials(self) -> tuple:
"""从commands中提取用户名和密码"""
username = None
password = None
for cmd in self.commands:
if cmd.startswith("!username="):
username = cmd.split("=")[1]
elif cmd.startswith("!password="):
password = cmd.split("=")[1]
return username, password
def get_clean_commands(self) -> List[str]:
"""获取去除凭据后的实际命令"""
return [cmd for cmd in self.commands
if not (cmd.startswith("!username=") or cmd.startswith("!password="))]
@router.post("/execute_cli_commands", response_model=dict) @router.post("/execute_cli_commands", response_model=dict)
async def execute_cli_commands(request: CLICommandRequest): async def execute_cli_commands(request: CLICommandRequest):
""" """
执行前端生成的CLI命令 执行前端生成的CLI命令
支持在commands中嵌入凭据:
!username=admin
!password=cisco123
""" """
try: try:
username, password = request.extract_credentials()
clean_commands = request.get_clean_commands()
configurator = SwitchConfigurator( configurator = SwitchConfigurator(
username=settings.SWITCH_USERNAME, username=username,
password=settings.SWITCH_PASSWORD, password=password,
timeout=settings.SWITCH_TIMEOUT, timeout=settings.SWITCH_TIMEOUT,
ensp_mode=request.is_ensp ensp_mode=request.is_ensp
) )
@ -220,7 +253,7 @@ async def root():
} }
# ... 其他路由保持不变 ...
@router.get("/traffic/switch/interfaces", summary="获取交换机的网络接口") @router.get("/traffic/switch/interfaces", summary="获取交换机的网络接口")
async def get_switch_interfaces(switch_ip: str): async def get_switch_interfaces(switch_ip: str):

View File

@ -10,6 +10,8 @@ import asyncssh
from pydantic import BaseModel from pydantic import BaseModel
from tenacity import retry, stop_after_attempt, wait_exponential from tenacity import retry, stop_after_attempt, wait_exponential
from src.backend.config import settings
# ---------------------- # ----------------------
# 数据模型 # 数据模型
@ -44,18 +46,18 @@ class SSHConnectionException(SwitchConfigException):
class SwitchConfigurator: class SwitchConfigurator:
def __init__( def __init__(
self, self,
username: str = "admin", username: str = None,
password: str = "admin", password: str = None,
timeout: int = 10, timeout: int = None,
max_workers: int = 5, max_workers: int = 5,
ensp_mode: bool = False, ensp_mode: bool = False,
ensp_port: int = 2000, ensp_port: int = 2000,
ensp_command_delay: float = 0.5, ensp_command_delay: float = 0.5,
**ssh_options **ssh_options
): ):
self.username = username self.username = username if username is not None else settings.SWITCH_USERNAME
self.password = password self.password = password if password is not None else settings.SWITCH_PASSWORD
self.timeout = timeout self.timeout = timeout if timeout is not None else settings.SWITCH_TIMEOUT
self.semaphore = asyncio.Semaphore(max_workers) self.semaphore = asyncio.Semaphore(max_workers)
self.backup_dir = Path("config_backups") self.backup_dir = Path("config_backups")
self.backup_dir.mkdir(exist_ok=True) self.backup_dir.mkdir(exist_ok=True)

View File

@ -16,6 +16,8 @@ class Settings(BaseSettings):
SILICONFLOW_API_URL: str = os.getenv("SILICONFLOW_API_URL", "https://api.siliconflow.cn/v1") SILICONFLOW_API_URL: str = os.getenv("SILICONFLOW_API_URL", "https://api.siliconflow.cn/v1")
# 交换机配置 # 交换机配置
SWITCH_USERNAME: str = os.getenv("SWITCH_USERNAME", "admin")
SWITCH_PASSWORD: str = os.getenv("SWITCH_PASSWORD", "admin")
SWITCH_TIMEOUT: int = os.getenv("SWITCH_TIMEOUT", 10) SWITCH_TIMEOUT: int = os.getenv("SWITCH_TIMEOUT", 10)
# eNSP配置 # eNSP配置

View File

@ -1,7 +1,6 @@
fastapi==0.110.0 fastapi==0.110.0
uvicorn==0.29.0 uvicorn==0.29.0
python-dotenv==1.0.1 python-dotenv==1.0.1
pysnmp
pydantic==2.6.4 pydantic==2.6.4
pydantic-settings==2.2.1 pydantic-settings==2.2.1
openai==1.93.2 openai==1.93.2