diff --git a/src/backend/app/api/endpoints.py b/src/backend/app/api/endpoints.py index 0f92bc1..8bbda72 100644 --- a/src/backend/app/api/endpoints.py +++ b/src/backend/app/api/endpoints.py @@ -186,7 +186,7 @@ async def execute_cli_commands(request: CLICommandRequest): ) return { "success": True, - "output": result, + "output": result, "mode": "eNSP" if request.is_ensp else "SSH" } except Exception as e: diff --git a/src/backend/app/api/network_config.py b/src/backend/app/api/network_config.py index 3141a62..3a84bae 100644 --- a/src/backend/app/api/network_config.py +++ b/src/backend/app/api/network_config.py @@ -10,6 +10,7 @@ import asyncssh from pydantic import BaseModel from tenacity import retry, stop_after_attempt, wait_exponential +from src.backend.app.utils.logger import logger from src.backend.config import settings @@ -82,56 +83,62 @@ class SwitchConfigurator: """双模式命令发送""" return ( await self._send_ensp_commands(ip, commands) - if self.ensp_mode - else await self._send_ssh_commands(ip, commands) ) - # --------- eNSP模式专用 --------- async def _send_ensp_commands(self, ip: str, commands: List[str]) -> str: - """Telnet协议执行(eNSP)""" + """ + 通过 Telnet 协议连接 eNSP 设备 + """ try: - reader, writer = await telnetlib3.open_connection( - host=ip, - port=self.ensp_port, - connect_minwait=self.timeout, # telnetlib3的实际可用参数 - connect_maxwait=self.timeout - ) + logger.info(f"连接设备 {ip},端口23") + reader, writer = await telnetlib3.open_connection(host=ip, port=23) + logger.debug("连接成功,开始登录流程") try: - await asyncio.wait_for(reader.readuntil(b"Username:"), timeout=self.timeout) - writer.write(f"{self.username}\n") + if self.username != 'NONE': + await asyncio.wait_for(reader.readuntil(b"Username:"), timeout=self.timeout) + logger.debug("收到 'Username:' 提示,发送用户名") + writer.write(f"{self.username}\n") await asyncio.wait_for(reader.readuntil(b"Password:"), timeout=self.timeout) + logger.debug("收到 'Password:' 提示,发送密码") writer.write(f"{self.password}\n") await asyncio.sleep(1) except asyncio.TimeoutError: - raise EnspConnectionException("登录超时") + raise EnspConnectionException("登录超时,未收到用户名或密码提示") output = "" for cmd in commands: + logger.info(f"发送命令: {cmd}") writer.write(f"{cmd}\n") await writer.drain() + command_output = "" try: while True: data = await asyncio.wait_for(reader.read(1024), timeout=1) if not data: + logger.debug("读取到空数据,结束当前命令读取") break - output += data + command_output += data + logger.debug(f"收到数据: {repr(data)}") except asyncio.TimeoutError: - continue + logger.debug("命令输出读取超时,继续执行下一条命令") + output += f"\n[命令: {cmd} 输出开始]\n{command_output}\n[命令: {cmd} 输出结束]\n" + + logger.info("所有命令执行完毕,关闭连接") writer.close() - try: - await writer.wait_closed() - except: - logging.debug("连接关闭时出现异常", exc_info=True) - pass return output + + except asyncio.TimeoutError as e: + logger.error(f"连接或读取超时: {e}") + raise EnspConnectionException(f"eNSP连接超时: {e}") except Exception as e: - raise EnspConnectionException(f"eNSP连接失败: {str(e)}") + logger.error(f"连接或执行异常: {e}", exc_info=True) + raise EnspConnectionException(f"eNSP连接失败: {e}") @staticmethod def _generate_ensp_commands(config: SwitchConfig) -> List[str]: diff --git a/src/backend/app/services/ai_services.py b/src/backend/app/services/ai_services.py index da000c8..d315477 100644 --- a/src/backend/app/services/ai_services.py +++ b/src/backend/app/services/ai_services.py @@ -30,10 +30,10 @@ class AIService: 2. 必须包含'commands'字段,包含可直接执行的命令列表 3. 其他参数根据配置类型动态添加 4. 不要包含解释性文本、步骤说明或注释 - 5.要包含使用ssh连接交换机后的完整命令包括但不完全包括system-view,退出,保存等完整操作,注意保存还需要输入Y和回车 + 5.要包含使用ssh连接交换机后的完整命令包括但不完全包括system-view,退出,保存等完整操作,注意保存还需要输入Y 示例命令:'创建VLAN 100,名称为TEST' - 示例返回:{"type": "vlan", "vlan_id": 100, "name": "TEST", "commands": ["system-view\n","vlan 100\n", "name TEST\n","quit\n","\x1A\n","save\n","Y\n"]} - 注意:这里生成的commands中需包含登录交换机和保存等所有操作命令,我们使ssh连接交换机,你不需要给出连接ssh的命令,你只需要给出使用ssh连接到交换机后所输入的全部命令 + 示例返回:{"type": "vlan", "vlan_id": 100, "name": "TEST", "commands": ["system-view","vlan 100", "name TEST","quit","quit","save","Y"]} + 注意:这里生成的commands中需包含登录交换机和保存等所有操作命令,我们使ssh连接交换机,你不需要给出连接ssh的命令,你只需要给出使用ssh连接到交换机后所输入的全部命令,并且注意在system-view状态下是不能save的,需要再quit到用户视图 """ messages = [ diff --git a/src/frontend/src/pages/ConfigPage.jsx b/src/frontend/src/pages/ConfigPage.jsx index 93a8423..8591ce9 100644 --- a/src/frontend/src/pages/ConfigPage.jsx +++ b/src/frontend/src/pages/ConfigPage.jsx @@ -117,16 +117,31 @@ const ConfigPage = () => { console.log(`commands:${JSON.stringify(commands)}`); const deviceConfig = JSON.parse(selectedDeviceConfig); console.log(`deviceConfig:${JSON.stringify(deviceConfig)}`); - if (!deviceConfig.username || !deviceConfig.password) { + if (!deviceConfig.password) { Notification.warn({ - title: '所选交换机暂未配置用户名和密码', + title: '所选交换机暂未配置用户名(可选)和密码', description: '请前往交换机设备处配置username和password', }); return false; } - commands.push(`username=${deviceConfig.username.toString()}`); - commands.push(`password=${deviceConfig.password.toString()}`); - await api.applyConfig(selectedDevice, commands); + if (deviceConfig.username || deviceConfig.username.toString() !== '') { + commands.push(`!username=${deviceConfig.username.toString()}`); + } else { + commands.push(`!username=NONE`); + } + commands.push(`!password=${deviceConfig.password.toString()}`); + const res = await api.applyConfig(selectedDevice, commands); + if (res) { + Notification.success({ + title: '配置完毕', + description: JSON.stringify(res), + }); + } else { + Notification.error({ + title: '配置过程出现错误', + description: '请检查API提示', + }); + } } }; diff --git a/src/frontend/src/pages/ScanPage.jsx b/src/frontend/src/pages/ScanPage.jsx index ef6f82b..f5ee07d 100644 --- a/src/frontend/src/pages/ScanPage.jsx +++ b/src/frontend/src/pages/ScanPage.jsx @@ -197,7 +197,7 @@ const ScanPage = () => { {inputMode ? ( - + { {loading && ( - - - {'正在加载,请稍候..'} - + + + + {'正在扫描,请稍候..'} + + )} {!loading && scannedDevices.length > 0 && ( - + { )} {!loading && scannedDevices.length === 0 && ( - {'暂无扫描结果,请执行扫描..'} + + {'暂无扫描结果,请执行扫描..'} + )}