mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-10-14 09:49:19 +00:00
feat:telnet发送配置命令
This commit is contained in:
parent
c74d55e62b
commit
2af76f8a54
@ -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:
|
||||
|
@ -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]:
|
||||
|
@ -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 = [
|
||||
|
@ -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提示',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -197,7 +197,7 @@ const ScanPage = () => {
|
||||
</HStack>
|
||||
|
||||
{inputMode ? (
|
||||
<FadeInWrapper delay={0.1} yOffset={-2}>
|
||||
<FadeInWrapper delay={0} yOffset={-1}>
|
||||
<Input
|
||||
mb={4}
|
||||
placeholder={'输入子网 (如 192.168.1.0/24)'}
|
||||
@ -229,14 +229,16 @@ const ScanPage = () => {
|
||||
</HStack>
|
||||
|
||||
{loading && (
|
||||
<HStack>
|
||||
<Spinner />
|
||||
<Text>{'正在加载,请稍候..'}</Text>
|
||||
</HStack>
|
||||
<FadeInWrapper delay={0} yOffset={-1}>
|
||||
<HStack>
|
||||
<Spinner />
|
||||
<Text>{'正在扫描,请稍候..'}</Text>
|
||||
</HStack>
|
||||
</FadeInWrapper>
|
||||
)}
|
||||
|
||||
{!loading && scannedDevices.length > 0 && (
|
||||
<FadeInWrapper delay={0.2} yOffset={-5}>
|
||||
<FadeInWrapper delay={0} yOffset={-3}>
|
||||
<Table.Root
|
||||
variant={'outline'}
|
||||
colorPalette={'teal'}
|
||||
@ -283,7 +285,9 @@ const ScanPage = () => {
|
||||
)}
|
||||
|
||||
{!loading && scannedDevices.length === 0 && (
|
||||
<Text color={'gray.400'}>{'暂无扫描结果,请执行扫描..'}</Text>
|
||||
<FadeInWrapper delay={0} yOffset={-1}>
|
||||
<Text color={'gray.400'}>{'暂无扫描结果,请执行扫描..'}</Text>
|
||||
</FadeInWrapper>
|
||||
)}
|
||||
</Box>
|
||||
</VStack>
|
||||
|
Loading…
x
Reference in New Issue
Block a user