✨ 新增终端连接状态展示
This commit is contained in:
parent
e0eb1446db
commit
1e783c0d90
@ -158,6 +158,7 @@ module.exports = (httpServer) => {
|
||||
// 初始化新的SSH客户端对象
|
||||
sshClient = new SSHClient()
|
||||
stream = await createTerminal(ip, socket, sshClient)
|
||||
socket.emit('reconnect_terminal_success')
|
||||
socket.on('input', listenerInput)
|
||||
socket.on('resize', resizeShell)
|
||||
}, 3000)
|
||||
|
15
web/src/utils/enum.js
Normal file
15
web/src/utils/enum.js
Normal file
@ -0,0 +1,15 @@
|
||||
// 终端连接状态
|
||||
export const terminalStatus = {
|
||||
CONNECTING: 'connecting',
|
||||
RECONNECTING: 'reconnecting',
|
||||
CONNECT_FAIL: 'connect_fail',
|
||||
CONNECT_SUCCESS: 'connect_success'
|
||||
}
|
||||
export const terminalStatusList = [
|
||||
{ value: terminalStatus.CONNECTING, label: '连接中', color: '#FFA500' },
|
||||
{ value: terminalStatus.RECONNECTING, label: '重连中', color: '#FFA500' },
|
||||
{ value: terminalStatus.CONNECT_FAIL, label: '连接失败', color: '#DC3545' },
|
||||
{ value: terminalStatus.CONNECT_SUCCESS, label: '已连接', color: '#28A745' },
|
||||
]
|
||||
|
||||
// other...
|
@ -21,14 +21,17 @@ import { SearchAddon } from '@xterm/addon-search'
|
||||
// import { SearchBarAddon } from 'xterm-addon-search-bar'
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links'
|
||||
import socketIo from 'socket.io-client'
|
||||
import { terminalStatus } from '@/utils/enum'
|
||||
|
||||
const { CONNECTING, RECONNECTING, CONNECT_SUCCESS, CONNECT_FAIL } = terminalStatus
|
||||
|
||||
const { io } = socketIo
|
||||
const { proxy: { $api, $store, $serviceURI, $notification, $router, $message } } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
host: {
|
||||
hostObj: {
|
||||
required: true,
|
||||
type: String
|
||||
type: Object
|
||||
},
|
||||
fontSize: {
|
||||
required: false,
|
||||
@ -54,11 +57,13 @@ const command = ref('')
|
||||
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)
|
||||
const hasRegisterEvent = ref(false)
|
||||
|
||||
// const isConnectSuccess = ref(false)
|
||||
// const isConnectFail = ref(false)
|
||||
// const isConnecting = ref(true)
|
||||
// const isReConnect = ref(false)
|
||||
const curStatus = ref(CONNECTING)
|
||||
const terminal = ref(null)
|
||||
const terminalRef = ref(null)
|
||||
|
||||
@ -66,6 +71,8 @@ const token = computed(() => $store.token)
|
||||
const theme = computed(() => props.theme)
|
||||
const fontSize = computed(() => props.fontSize)
|
||||
const background = computed(() => props.background)
|
||||
const hostObj = computed(() => props.hostObj)
|
||||
const host = computed(() => hostObj.value.host)
|
||||
|
||||
watch(theme, () => {
|
||||
nextTick(() => {
|
||||
@ -96,28 +103,28 @@ watch(background, (newVal) => {
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
watch(curStatus, () => {
|
||||
console.warn(`status: ${ curStatus.value }`)
|
||||
hostObj.value.status = curStatus.value
|
||||
})
|
||||
|
||||
const getCommand = async () => {
|
||||
let { data } = await $api.getCommand(props.host)
|
||||
let { data } = await $api.getCommand(host.value)
|
||||
if (data) command.value = data
|
||||
}
|
||||
|
||||
const connectIO = () => {
|
||||
const { host } = props
|
||||
socket.value = io($serviceURI, {
|
||||
path: '/terminal',
|
||||
forceNew: false,
|
||||
reconnectionAttempts: 1
|
||||
})
|
||||
socket.value.on('connect', () => {
|
||||
console.log('/terminal socket已连接:', host)
|
||||
socket.value.emit('create', { host, token: token.value })
|
||||
console.log('/terminal socket已连接:', host.value)
|
||||
socket.value.emit('create', { host: host.value, token: token.value })
|
||||
socket.value.on('connect_terminal_success', () => {
|
||||
isConnectFail.value = false
|
||||
isConnecting.value = false
|
||||
if (isReConnect.value) {
|
||||
isReConnect.value = false
|
||||
return // 重连不需要再注册监听事件
|
||||
}
|
||||
if (hasRegisterEvent.value) return // 以下事件连接成功后仅可注册一次, 否则会多次触发. 除非socket重连
|
||||
hasRegisterEvent.value = true
|
||||
|
||||
socket.value.on('output', (str) => {
|
||||
term.value.write(str)
|
||||
@ -125,7 +132,7 @@ const connectIO = () => {
|
||||
})
|
||||
|
||||
socket.value.on('connect_shell_success', () => {
|
||||
isConnectSuccess.value = true
|
||||
curStatus.value = CONNECT_SUCCESS
|
||||
onResize()
|
||||
onFindText()
|
||||
onWebLinks()
|
||||
@ -144,28 +151,26 @@ const connectIO = () => {
|
||||
})
|
||||
|
||||
socket.value.on('connect_close', () => {
|
||||
if (isConnectFail.value) return
|
||||
isReConnect.value = true // 重连状态标记为true
|
||||
isConnecting.value = true
|
||||
isConnectSuccess.value = false
|
||||
console.warn('连接断开,3秒后重连: ', host)
|
||||
if (curStatus.value === CONNECT_FAIL) return // 连接失败不需要自动重连
|
||||
curStatus.value = RECONNECTING
|
||||
console.warn('连接断开,3秒后自动重连: ', host.value)
|
||||
term.value.write('\r\n连接断开,3秒后自动重连...\r\n')
|
||||
socket.value.emit('reconnect_terminal')
|
||||
})
|
||||
|
||||
socket.value.on('reconnect_terminal_success', () => {
|
||||
curStatus.value = CONNECT_SUCCESS
|
||||
})
|
||||
|
||||
socket.value.on('create_fail', (message) => {
|
||||
isConnectFail.value = true
|
||||
isConnecting.value = false
|
||||
isConnectSuccess.value = false
|
||||
console.error('n创建失败:', host, message)
|
||||
curStatus.value = CONNECT_FAIL
|
||||
console.error('n创建失败:', host.value, message)
|
||||
term.value.write(`\r\n创建失败: ${ message }\r\n`)
|
||||
})
|
||||
|
||||
socket.value.on('connect_fail', (message) => {
|
||||
isConnectFail.value = true
|
||||
isConnecting.value = false
|
||||
isConnectSuccess.value = false
|
||||
console.error('连接失败:', host, message)
|
||||
curStatus.value = CONNECT_FAIL
|
||||
console.error('连接失败:', host.value, message)
|
||||
term.value.write(`\r\n连接失败: ${ message }\r\n`)
|
||||
})
|
||||
})
|
||||
@ -174,10 +179,8 @@ const connectIO = () => {
|
||||
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' })
|
||||
curStatus.value = CONNECT_FAIL
|
||||
term.value.write('\r\nError: 与面板socket连接断开。请关闭此tab,并检查本地与面板连接是否稳定\r\n')
|
||||
})
|
||||
|
||||
socket.value.on('connect_error', (err) => {
|
||||
@ -316,13 +319,13 @@ const onData = () => {
|
||||
enterTimer.value = setTimeout(() => {
|
||||
if (enterTimer.value) clearTimeout(enterTimer.value)
|
||||
if (key === '\r') { // Enter
|
||||
if (isConnectFail.value && !isConnecting.value) { // 连接失败&&未正在连接,按回车可触发重连
|
||||
isConnecting.value = true
|
||||
if (curStatus.value === CONNECT_FAIL) { // 连接失败&&未正在连接,按回车可触发重连
|
||||
curStatus.value = CONNECTING
|
||||
term.value.write('\r\n连接中...\r\n')
|
||||
socket.value.emit('reconnect_terminal')
|
||||
return
|
||||
}
|
||||
if (isConnectSuccess.value) {
|
||||
if (curStatus.value === CONNECT_SUCCESS) {
|
||||
let cleanText = applyBackspace(filterAnsiSequences(terminalText.value))
|
||||
const lines = cleanText.split('\n')
|
||||
// console.log('lines: ', lines)
|
||||
@ -345,7 +348,7 @@ const onData = () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
if (isConnectFail.value || isConnecting.value) return console.warn(`isConnectFail: ${ isConnectFail.value }, isConnecting: ${ isConnecting.value }`)
|
||||
if (curStatus.value !== CONNECT_SUCCESS) return
|
||||
emit('inputCommand', key)
|
||||
socket.value.emit('input', key)
|
||||
})
|
||||
@ -393,11 +396,11 @@ onMounted(async () => {
|
||||
createLocalTerminal()
|
||||
await getCommand()
|
||||
connectIO()
|
||||
await nextTick()
|
||||
onData()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
isManual.value = true
|
||||
socket.value?.close()
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
@ -119,10 +119,16 @@
|
||||
:closable="true"
|
||||
class="el_tab_pane"
|
||||
>
|
||||
<template #label>
|
||||
<div class="tab_label">
|
||||
<span class="tab_status" :style="{ background: getStatusColor(item.status) }" />
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tab_content_wrap" :style="{ height: mainHeight + 'px' }">
|
||||
<TerminalTab
|
||||
ref="terminalRefs"
|
||||
:host="item.host"
|
||||
:host-obj="item"
|
||||
:theme="themeObj"
|
||||
:background="terminalBackground"
|
||||
:font-size="terminalFontSize"
|
||||
@ -165,8 +171,8 @@ import Sftp from './sftp.vue'
|
||||
import InputCommand from '@/components/input-command/index.vue'
|
||||
import HostForm from '../../server/components/host-form.vue'
|
||||
import TerminalSetting from './terminal-setting.vue'
|
||||
// import { randomStr } from '@utils/index.js'
|
||||
import themeList from 'xterm-theme'
|
||||
import { terminalStatusList } from '@/utils/enum'
|
||||
|
||||
const { proxy: { $nextTick, $store, $message } } = getCurrentInstance()
|
||||
|
||||
@ -226,6 +232,10 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResizeTerminalSftp)
|
||||
})
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
return terminalStatusList.find(item => item.value === status)?.color || 'gray'
|
||||
}
|
||||
|
||||
const handleUpdateList = async ({ host }) => {
|
||||
try {
|
||||
await $store.getHostList()
|
||||
@ -461,6 +471,19 @@ const handleInputCommand = async (command) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.tab_label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.tab_status {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
// background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.tab_content_wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -57,6 +57,8 @@ import { useRoute } from 'vue-router'
|
||||
import Terminal from './components/terminal.vue'
|
||||
import HostForm from '../server/components/host-form.vue'
|
||||
import { randomStr } from '@utils/index.js'
|
||||
import { terminalStatus } from '@/utils/enum'
|
||||
const { CONNECTING } = terminalStatus
|
||||
|
||||
const { proxy: { $store, $message } } = getCurrentInstance()
|
||||
|
||||
@ -74,7 +76,7 @@ let isAllConfssh = computed(() => {
|
||||
|
||||
function linkTerminal(row) {
|
||||
const { name, host } = row
|
||||
terminalTabs.push({ key: randomStr(16), name, host })
|
||||
terminalTabs.push({ key: randomStr(16), name, host, status: CONNECTING })
|
||||
}
|
||||
|
||||
function handleUpdateHost(row) {
|
||||
@ -103,7 +105,7 @@ onActivated(async () => {
|
||||
if (!host) return
|
||||
let targetHosts = hostList.value.filter(item => host.includes(item.host)).map(item => {
|
||||
const { name, host } = item
|
||||
return { key: randomStr(16), name, host }
|
||||
return { key: randomStr(16), name, host, status: CONNECTING }
|
||||
})
|
||||
if (!targetHosts || !targetHosts.length) return
|
||||
terminalTabs.push(...targetHosts)
|
||||
|
Loading…
x
Reference in New Issue
Block a user