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
@ -10,6 +10,7 @@ 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.app.utils.logger import logger
|
||||||
from src.backend.config import settings
|
from src.backend.config import settings
|
||||||
|
|
||||||
|
|
||||||
@ -82,56 +83,62 @@ class SwitchConfigurator:
|
|||||||
"""双模式命令发送"""
|
"""双模式命令发送"""
|
||||||
return (
|
return (
|
||||||
await self._send_ensp_commands(ip, commands)
|
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:
|
async def _send_ensp_commands(self, ip: str, commands: List[str]) -> str:
|
||||||
"""Telnet协议执行(eNSP)"""
|
"""
|
||||||
|
通过 Telnet 协议连接 eNSP 设备
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
reader, writer = await telnetlib3.open_connection(
|
logger.info(f"连接设备 {ip},端口23")
|
||||||
host=ip,
|
reader, writer = await telnetlib3.open_connection(host=ip, port=23)
|
||||||
port=self.ensp_port,
|
logger.debug("连接成功,开始登录流程")
|
||||||
connect_minwait=self.timeout, # telnetlib3的实际可用参数
|
|
||||||
connect_maxwait=self.timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if self.username != 'NONE':
|
||||||
await asyncio.wait_for(reader.readuntil(b"Username:"), timeout=self.timeout)
|
await asyncio.wait_for(reader.readuntil(b"Username:"), timeout=self.timeout)
|
||||||
|
logger.debug("收到 'Username:' 提示,发送用户名")
|
||||||
writer.write(f"{self.username}\n")
|
writer.write(f"{self.username}\n")
|
||||||
|
|
||||||
await asyncio.wait_for(reader.readuntil(b"Password:"), timeout=self.timeout)
|
await asyncio.wait_for(reader.readuntil(b"Password:"), timeout=self.timeout)
|
||||||
|
logger.debug("收到 'Password:' 提示,发送密码")
|
||||||
writer.write(f"{self.password}\n")
|
writer.write(f"{self.password}\n")
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
raise EnspConnectionException("登录超时")
|
raise EnspConnectionException("登录超时,未收到用户名或密码提示")
|
||||||
|
|
||||||
output = ""
|
output = ""
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
|
logger.info(f"发送命令: {cmd}")
|
||||||
writer.write(f"{cmd}\n")
|
writer.write(f"{cmd}\n")
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
|
command_output = ""
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await asyncio.wait_for(reader.read(1024), timeout=1)
|
data = await asyncio.wait_for(reader.read(1024), timeout=1)
|
||||||
if not data:
|
if not data:
|
||||||
|
logger.debug("读取到空数据,结束当前命令读取")
|
||||||
break
|
break
|
||||||
output += data
|
command_output += data
|
||||||
|
logger.debug(f"收到数据: {repr(data)}")
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
continue
|
logger.debug("命令输出读取超时,继续执行下一条命令")
|
||||||
|
|
||||||
|
output += f"\n[命令: {cmd} 输出开始]\n{command_output}\n[命令: {cmd} 输出结束]\n"
|
||||||
|
|
||||||
|
logger.info("所有命令执行完毕,关闭连接")
|
||||||
writer.close()
|
writer.close()
|
||||||
try:
|
|
||||||
await writer.wait_closed()
|
|
||||||
except:
|
|
||||||
logging.debug("连接关闭时出现异常", exc_info=True)
|
|
||||||
pass
|
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as e:
|
||||||
|
logger.error(f"连接或读取超时: {e}")
|
||||||
|
raise EnspConnectionException(f"eNSP连接超时: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise EnspConnectionException(f"eNSP连接失败: {str(e)}")
|
logger.error(f"连接或执行异常: {e}", exc_info=True)
|
||||||
|
raise EnspConnectionException(f"eNSP连接失败: {e}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_ensp_commands(config: SwitchConfig) -> List[str]:
|
def _generate_ensp_commands(config: SwitchConfig) -> List[str]:
|
||||||
|
@ -30,10 +30,10 @@ class AIService:
|
|||||||
2. 必须包含'commands'字段,包含可直接执行的命令列表
|
2. 必须包含'commands'字段,包含可直接执行的命令列表
|
||||||
3. 其他参数根据配置类型动态添加
|
3. 其他参数根据配置类型动态添加
|
||||||
4. 不要包含解释性文本、步骤说明或注释
|
4. 不要包含解释性文本、步骤说明或注释
|
||||||
5.要包含使用ssh连接交换机后的完整命令包括但不完全包括system-view,退出,保存等完整操作,注意保存还需要输入Y和回车
|
5.要包含使用ssh连接交换机后的完整命令包括但不完全包括system-view,退出,保存等完整操作,注意保存还需要输入Y
|
||||||
示例命令:'创建VLAN 100,名称为TEST'
|
示例命令:'创建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"]}
|
示例返回:{"type": "vlan", "vlan_id": 100, "name": "TEST", "commands": ["system-view","vlan 100", "name TEST","quit","quit","save","Y"]}
|
||||||
注意:这里生成的commands中需包含登录交换机和保存等所有操作命令,我们使ssh连接交换机,你不需要给出连接ssh的命令,你只需要给出使用ssh连接到交换机后所输入的全部命令
|
注意:这里生成的commands中需包含登录交换机和保存等所有操作命令,我们使ssh连接交换机,你不需要给出连接ssh的命令,你只需要给出使用ssh连接到交换机后所输入的全部命令,并且注意在system-view状态下是不能save的,需要再quit到用户视图
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
|
@ -117,16 +117,31 @@ const ConfigPage = () => {
|
|||||||
console.log(`commands:${JSON.stringify(commands)}`);
|
console.log(`commands:${JSON.stringify(commands)}`);
|
||||||
const deviceConfig = JSON.parse(selectedDeviceConfig);
|
const deviceConfig = JSON.parse(selectedDeviceConfig);
|
||||||
console.log(`deviceConfig:${JSON.stringify(deviceConfig)}`);
|
console.log(`deviceConfig:${JSON.stringify(deviceConfig)}`);
|
||||||
if (!deviceConfig.username || !deviceConfig.password) {
|
if (!deviceConfig.password) {
|
||||||
Notification.warn({
|
Notification.warn({
|
||||||
title: '所选交换机暂未配置用户名和密码',
|
title: '所选交换机暂未配置用户名(可选)和密码',
|
||||||
description: '请前往交换机设备处配置username和password',
|
description: '请前往交换机设备处配置username和password',
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
commands.push(`username=${deviceConfig.username.toString()}`);
|
if (deviceConfig.username || deviceConfig.username.toString() !== '') {
|
||||||
commands.push(`password=${deviceConfig.password.toString()}`);
|
commands.push(`!username=${deviceConfig.username.toString()}`);
|
||||||
await api.applyConfig(selectedDevice, commands);
|
} 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>
|
</HStack>
|
||||||
|
|
||||||
{inputMode ? (
|
{inputMode ? (
|
||||||
<FadeInWrapper delay={0.1} yOffset={-2}>
|
<FadeInWrapper delay={0} yOffset={-1}>
|
||||||
<Input
|
<Input
|
||||||
mb={4}
|
mb={4}
|
||||||
placeholder={'输入子网 (如 192.168.1.0/24)'}
|
placeholder={'输入子网 (如 192.168.1.0/24)'}
|
||||||
@ -229,14 +229,16 @@ const ScanPage = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
|
<FadeInWrapper delay={0} yOffset={-1}>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<Text>{'正在加载,请稍候..'}</Text>
|
<Text>{'正在扫描,请稍候..'}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
</FadeInWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && scannedDevices.length > 0 && (
|
{!loading && scannedDevices.length > 0 && (
|
||||||
<FadeInWrapper delay={0.2} yOffset={-5}>
|
<FadeInWrapper delay={0} yOffset={-3}>
|
||||||
<Table.Root
|
<Table.Root
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
colorPalette={'teal'}
|
colorPalette={'teal'}
|
||||||
@ -283,7 +285,9 @@ const ScanPage = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && scannedDevices.length === 0 && (
|
{!loading && scannedDevices.length === 0 && (
|
||||||
|
<FadeInWrapper delay={0} yOffset={-1}>
|
||||||
<Text color={'gray.400'}>{'暂无扫描结果,请执行扫描..'}</Text>
|
<Text color={'gray.400'}>{'暂无扫描结果,请执行扫描..'}</Text>
|
||||||
|
</FadeInWrapper>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user