优化终端连接逻辑

This commit is contained in:
chaos-zhu 2024-08-15 08:16:38 +08:00
parent 21a97bf375
commit e0eb1446db
5 changed files with 63 additions and 43 deletions

View File

@ -2,6 +2,7 @@
### Features
* 终端连接逻辑重写,断线自动重连 ✔
* 终端支持选中复制&右键粘贴 ✔
* 终端设置支持字体大小 ✔
* 终端默认字体样式更改为`Cascadia Code`

View File

@ -16,7 +16,7 @@ function createInteractiveShell(socket, sshClient) {
consola.info('交互终端已关闭')
sshClient.end()
})
socket.emit('connect_terminal') // 已连接终端web端可以执行指令了
socket.emit('connect_shell_success') // 已连接终端web端可以执行指令了
// web端输入
// socket.on('input', key => {
// if (sshClient._sock.writable === false) return consola.info('终端连接已关闭,禁止输入')
@ -80,7 +80,7 @@ async function createTerminal(ip, socket, sshClient) {
sshClient
.on('ready', async() => {
consola.success('终端连接成功:', host)
socket.emit('connect_success', `终端连接成功:${ host }`)
socket.emit('connect_terminal_success', `终端连接成功:${ host }`)
let stream = await createInteractiveShell(socket, sshClient)
resolve(stream)
// execShell(sshClient, 'history', (data) => {
@ -90,12 +90,12 @@ async function createTerminal(ip, socket, sshClient) {
// })
})
.on('close', () => {
consola.info('终端连接断开close')
consola.info('终端连接断开close: ', host)
socket.emit('connect_close')
})
.on('error', (err) => {
consola.log(err)
consola.error('连接终端失败:', err.level)
consola.error('连接终端失败:', host, err.message)
socket.emit('connect_fail', err.message)
})
.connect({
@ -103,7 +103,7 @@ async function createTerminal(ip, socket, sshClient) {
// debug: (info) => console.log(info)
})
} catch (err) {
consola.error('创建终端失败:', err.message)
consola.error('创建终端失败: ', host, err.message)
socket.emit('create_fail', err.message)
}
})
@ -134,7 +134,7 @@ module.exports = (httpServer) => {
// setTimeout(() => {
// sshClient.end()
// }, 3000)
let stream = await createTerminal(ip, socket, sshClient)
let stream = null
function listenerInput(key) {
if (sshClient._sock.writable === false) return consola.info('终端连接已关闭,禁止输入')
@ -148,6 +148,7 @@ module.exports = (httpServer) => {
socket.on('resize', resizeShell)
// 重连
socket.on('reconnect_terminal', async () => {
consola.info('重连终端: ', ip)
socket.off('input', listenerInput) // 取消监听,重新注册监听,操作新的stream
socket.off('resize', resizeShell)
sshClient?.end()
@ -161,6 +162,7 @@ module.exports = (httpServer) => {
socket.on('resize', resizeShell)
}, 3000)
})
stream = await createTerminal(ip, socket, sshClient)
})
socket.on('disconnect', (reason) => {

View File

@ -3,20 +3,20 @@ html, body, div, ul, section, textarea {
box-sizing: border-box;
// 滚动条整体部分
&::-webkit-scrollbar {
height: 8px;
width: 2px;
height: 5px;
width: 5px;
background-color: #ffffff;
}
// 底层轨道
&::-webkit-scrollbar-track {
background-color: #ffffff;
border-radius: 10px;
border-radius: 3px;
}
// 滚动滑块
&::-webkit-scrollbar-thumb {
border-radius: 10px;
border-radius: 3px;
// background-color: #1989fa;
background-image: -webkit-gradient(linear, 40% 0%, 75% 84%, from(#a18cd1), to(#fbc2eb), color-stop(.6, #54DE5D));
}

View File

@ -55,6 +55,7 @@ const timer = ref(null)
const fitAddon = ref(null)
const searchBar = ref(null)
const isManual = ref(false)
const isConnectSuccess = ref(false)
const isConnectFail = ref(false)
const isConnecting = ref(true)
const isReConnect = ref(false)
@ -107,59 +108,75 @@ const connectIO = () => {
forceNew: false,
reconnectionAttempts: 1
})
socket.value.on('connect', () => {
console.log('/terminal socket已连接', socket.value.id)
console.log('/terminal socket已连接', host)
socket.value.emit('create', { host, token: token.value })
socket.value.on('connect_success', () => {
socket.value.on('connect_terminal_success', () => {
isConnectFail.value = false
isConnecting.value = false
if (isReConnect.value) {
isReConnect.value = false
return //
}
onData()
socket.value.on('connect_terminal', () => {
socket.value.on('output', (str) => {
term.value.write(str)
terminalText.value += str
})
socket.value.on('connect_shell_success', () => {
isConnectSuccess.value = true
onResize()
onFindText()
onWebLinks()
if (command.value) socket.value.emit('input', command.value + '\n')
})
// socket.value.on('terminal_command_history', (data) => {
// console.log(data)
// commandHistoryList.value = data
// })
})
socket.value.on('token_verify_fail', () => {
$notification({ title: 'Error', message: 'token校验失败请重新登录', type: 'error' })
$router.push('/login')
})
socket.value.on('connect_close', () => {
if (isConnectFail.value) return
isReConnect.value = true // true
isConnecting.value = true
console.warn('连接断开')
isConnectSuccess.value = false
console.warn('连接断开,3秒后重连: ', host)
term.value.write('\r\n连接断开,3秒后自动重连...\r\n')
// socket.value.removeAllListeners()
// socket.value.off('output') // output,onData
socket.value.emit('reconnect_terminal')
})
socket.value.on('create_fail', (message) => {
isConnectFail.value = true
isConnecting.value = false
console.error(message)
isConnectSuccess.value = false
console.error('n创建失败:', host, message)
term.value.write(`\r\n创建失败: ${ message }\r\n`)
})
socket.value.on('connect_fail', (message) => {
isConnectFail.value = true
isConnecting.value = false
console.error('连接失败:', message)
isConnectSuccess.value = false
console.error('连接失败:', host, message)
term.value.write(`\r\n连接失败: ${ message }\r\n`)
})
})
socket.value.on('disconnect', () => {
console.warn('terminal websocket 连接断开')
socket.value.removeAllListeners() //
// socket.value.off('output') // output,onData
isConnectFail.value = true
isConnecting.value = true
isConnectSuccess.value = false
if (!isManual.value) $notification({ title: '与面板socket连接断开', message: `${ props.host }-请检查socket服务是否稳定`, type: 'error' })
})
@ -291,11 +308,6 @@ function extractLastCdPath(text) {
}
const onData = () => {
socket.value.on('output', (str) => {
term.value.write(str)
terminalText.value += str
// console.log(terminalText.value)
})
// term.value.off('data', listenerInput)
term.value.onData((key) => {
let acsiiCode = key.codePointAt()
@ -305,31 +317,35 @@ const onData = () => {
if (enterTimer.value) clearTimeout(enterTimer.value)
if (key === '\r') { // Enter
if (isConnectFail.value && !isConnecting.value) { // &&
isConnecting.value = true
term.value.write('\r\n连接中...\r\n')
socket.value.emit('reconnect_terminal')
return
}
let cleanText = applyBackspace(filterAnsiSequences(terminalText.value))
const lines = cleanText.split('\n')
// console.log('lines: ', lines)
const lastLine = lines[lines.length - 1].trim()
// console.log('lastLine: ', lastLine)
// '$''#'
const commandStartIndex = lastLine.lastIndexOf('#') + 1
const commandText = lastLine.substring(commandStartIndex).trim()
// console.log('Processed command: ', commandText)
// eslint-disable-next-line
const cdPath = extractLastCdPath(commandText)
if (isConnectSuccess.value) {
let cleanText = applyBackspace(filterAnsiSequences(terminalText.value))
const lines = cleanText.split('\n')
// console.log('lines: ', lines)
const lastLine = lines[lines.length - 1].trim()
// console.log('lastLine: ', lastLine)
// '$''#'
const commandStartIndex = lastLine.lastIndexOf('#') + 1
const commandText = lastLine.substring(commandStartIndex).trim()
// console.log('Processed command: ', commandText)
// eslint-disable-next-line
const cdPath = extractLastCdPath(commandText)
if (cdPath) {
console.log('cd command path:', cdPath)
let firstChar = cdPath.charAt(0)
if (!['/',].includes(firstChar)) return console.log('err fullpath:', cdPath) // '~'
emit('cdCommand', cdPath)
if (cdPath) {
console.log('cd command path:', cdPath)
let firstChar = cdPath.charAt(0)
if (!['/',].includes(firstChar)) return console.log('err fullpath:', cdPath) // '~'
emit('cdCommand', cdPath)
}
terminalText.value = ''
}
terminalText.value = ''
}
})
if (isConnectFail.value || isConnecting.value) return console.warn(`isConnectFail: ${ isConnectFail.value }, isConnecting: ${ isConnecting.value }`)
emit('inputCommand', key)
socket.value.emit('input', key)
})
@ -377,6 +393,7 @@ onMounted(async () => {
createLocalTerminal()
await getCommand()
connectIO()
onData()
})
onBeforeUnmount(() => {

View File

@ -372,7 +372,7 @@ const handleInputCommand = async (command) => {
:deep(.el-tabs__content) {
flex: 1;
width: 100%;
padding: 0 5px 5px 0;
padding: 0 0 5px 0;
}
:deep(.el-tabs--border-card) {