新增终端连接状态展示

This commit is contained in:
chaos-zhu 2024-08-15 09:24:06 +08:00
parent e0eb1446db
commit 1e783c0d90
5 changed files with 88 additions and 44 deletions

View File

@ -158,6 +158,7 @@ module.exports = (httpServer) => {
// 初始化新的SSH客户端对象 // 初始化新的SSH客户端对象
sshClient = new SSHClient() sshClient = new SSHClient()
stream = await createTerminal(ip, socket, sshClient) stream = await createTerminal(ip, socket, sshClient)
socket.emit('reconnect_terminal_success')
socket.on('input', listenerInput) socket.on('input', listenerInput)
socket.on('resize', resizeShell) socket.on('resize', resizeShell)
}, 3000) }, 3000)

15
web/src/utils/enum.js Normal file
View 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...

View File

@ -21,14 +21,17 @@ import { SearchAddon } from '@xterm/addon-search'
// import { SearchBarAddon } from 'xterm-addon-search-bar' // import { SearchBarAddon } from 'xterm-addon-search-bar'
import { WebLinksAddon } from '@xterm/addon-web-links' import { WebLinksAddon } from '@xterm/addon-web-links'
import socketIo from 'socket.io-client' import socketIo from 'socket.io-client'
import { terminalStatus } from '@/utils/enum'
const { CONNECTING, RECONNECTING, CONNECT_SUCCESS, CONNECT_FAIL } = terminalStatus
const { io } = socketIo const { io } = socketIo
const { proxy: { $api, $store, $serviceURI, $notification, $router, $message } } = getCurrentInstance() const { proxy: { $api, $store, $serviceURI, $notification, $router, $message } } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
host: { hostObj: {
required: true, required: true,
type: String type: Object
}, },
fontSize: { fontSize: {
required: false, required: false,
@ -54,11 +57,13 @@ const command = ref('')
const timer = ref(null) const timer = ref(null)
const fitAddon = ref(null) const fitAddon = ref(null)
const searchBar = ref(null) const searchBar = ref(null)
const isManual = ref(false) const hasRegisterEvent = ref(false)
const isConnectSuccess = ref(false)
const isConnectFail = ref(false) // const isConnectSuccess = ref(false)
const isConnecting = ref(true) // const isConnectFail = ref(false)
const isReConnect = ref(false) // const isConnecting = ref(true)
// const isReConnect = ref(false)
const curStatus = ref(CONNECTING)
const terminal = ref(null) const terminal = ref(null)
const terminalRef = ref(null) const terminalRef = ref(null)
@ -66,6 +71,8 @@ const token = computed(() => $store.token)
const theme = computed(() => props.theme) const theme = computed(() => props.theme)
const fontSize = computed(() => props.fontSize) const fontSize = computed(() => props.fontSize)
const background = computed(() => props.background) const background = computed(() => props.background)
const hostObj = computed(() => props.hostObj)
const host = computed(() => hostObj.value.host)
watch(theme, () => { watch(theme, () => {
nextTick(() => { nextTick(() => {
@ -96,28 +103,28 @@ watch(background, (newVal) => {
}) })
}, { immediate: true }) }, { immediate: true })
watch(curStatus, () => {
console.warn(`status: ${ curStatus.value }`)
hostObj.value.status = curStatus.value
})
const getCommand = async () => { const getCommand = async () => {
let { data } = await $api.getCommand(props.host) let { data } = await $api.getCommand(host.value)
if (data) command.value = data if (data) command.value = data
} }
const connectIO = () => { const connectIO = () => {
const { host } = props
socket.value = io($serviceURI, { socket.value = io($serviceURI, {
path: '/terminal', path: '/terminal',
forceNew: false, forceNew: false,
reconnectionAttempts: 1 reconnectionAttempts: 1
}) })
socket.value.on('connect', () => { socket.value.on('connect', () => {
console.log('/terminal socket已连接', host) console.log('/terminal socket已连接', host.value)
socket.value.emit('create', { host, token: token.value }) socket.value.emit('create', { host: host.value, token: token.value })
socket.value.on('connect_terminal_success', () => { socket.value.on('connect_terminal_success', () => {
isConnectFail.value = false if (hasRegisterEvent.value) return // , . socket
isConnecting.value = false hasRegisterEvent.value = true
if (isReConnect.value) {
isReConnect.value = false
return //
}
socket.value.on('output', (str) => { socket.value.on('output', (str) => {
term.value.write(str) term.value.write(str)
@ -125,7 +132,7 @@ const connectIO = () => {
}) })
socket.value.on('connect_shell_success', () => { socket.value.on('connect_shell_success', () => {
isConnectSuccess.value = true curStatus.value = CONNECT_SUCCESS
onResize() onResize()
onFindText() onFindText()
onWebLinks() onWebLinks()
@ -144,28 +151,26 @@ const connectIO = () => {
}) })
socket.value.on('connect_close', () => { socket.value.on('connect_close', () => {
if (isConnectFail.value) return if (curStatus.value === CONNECT_FAIL) return //
isReConnect.value = true // true curStatus.value = RECONNECTING
isConnecting.value = true console.warn('连接断开,3秒后自动重连: ', host.value)
isConnectSuccess.value = false
console.warn('连接断开,3秒后重连: ', host)
term.value.write('\r\n连接断开,3秒后自动重连...\r\n') term.value.write('\r\n连接断开,3秒后自动重连...\r\n')
socket.value.emit('reconnect_terminal') socket.value.emit('reconnect_terminal')
}) })
socket.value.on('reconnect_terminal_success', () => {
curStatus.value = CONNECT_SUCCESS
})
socket.value.on('create_fail', (message) => { socket.value.on('create_fail', (message) => {
isConnectFail.value = true curStatus.value = CONNECT_FAIL
isConnecting.value = false console.error('n创建失败:', host.value, message)
isConnectSuccess.value = false
console.error('n创建失败:', host, message)
term.value.write(`\r\n创建失败: ${ message }\r\n`) term.value.write(`\r\n创建失败: ${ message }\r\n`)
}) })
socket.value.on('connect_fail', (message) => { socket.value.on('connect_fail', (message) => {
isConnectFail.value = true curStatus.value = CONNECT_FAIL
isConnecting.value = false console.error('连接失败:', host.value, message)
isConnectSuccess.value = false
console.error('连接失败:', host, message)
term.value.write(`\r\n连接失败: ${ message }\r\n`) term.value.write(`\r\n连接失败: ${ message }\r\n`)
}) })
}) })
@ -174,10 +179,8 @@ const connectIO = () => {
console.warn('terminal websocket 连接断开') console.warn('terminal websocket 连接断开')
socket.value.removeAllListeners() // socket.value.removeAllListeners() //
// socket.value.off('output') // output,onData // socket.value.off('output') // output,onData
isConnectFail.value = true curStatus.value = CONNECT_FAIL
isConnecting.value = true term.value.write('\r\nError: 与面板socket连接断开。请关闭此tab并检查本地与面板连接是否稳定\r\n')
isConnectSuccess.value = false
if (!isManual.value) $notification({ title: '与面板socket连接断开', message: `${ props.host }-请检查socket服务是否稳定`, type: 'error' })
}) })
socket.value.on('connect_error', (err) => { socket.value.on('connect_error', (err) => {
@ -316,13 +319,13 @@ const onData = () => {
enterTimer.value = setTimeout(() => { enterTimer.value = setTimeout(() => {
if (enterTimer.value) clearTimeout(enterTimer.value) if (enterTimer.value) clearTimeout(enterTimer.value)
if (key === '\r') { // Enter if (key === '\r') { // Enter
if (isConnectFail.value && !isConnecting.value) { // && if (curStatus.value === CONNECT_FAIL) { // &&
isConnecting.value = true curStatus.value = CONNECTING
term.value.write('\r\n连接中...\r\n') term.value.write('\r\n连接中...\r\n')
socket.value.emit('reconnect_terminal') socket.value.emit('reconnect_terminal')
return return
} }
if (isConnectSuccess.value) { if (curStatus.value === CONNECT_SUCCESS) {
let cleanText = applyBackspace(filterAnsiSequences(terminalText.value)) let cleanText = applyBackspace(filterAnsiSequences(terminalText.value))
const lines = cleanText.split('\n') const lines = cleanText.split('\n')
// console.log('lines: ', lines) // 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) emit('inputCommand', key)
socket.value.emit('input', key) socket.value.emit('input', key)
}) })
@ -393,11 +396,11 @@ onMounted(async () => {
createLocalTerminal() createLocalTerminal()
await getCommand() await getCommand()
connectIO() connectIO()
await nextTick()
onData() onData()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
isManual.value = true
socket.value?.close() socket.value?.close()
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
}) })

View File

@ -119,10 +119,16 @@
:closable="true" :closable="true"
class="el_tab_pane" 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' }"> <div class="tab_content_wrap" :style="{ height: mainHeight + 'px' }">
<TerminalTab <TerminalTab
ref="terminalRefs" ref="terminalRefs"
:host="item.host" :host-obj="item"
:theme="themeObj" :theme="themeObj"
:background="terminalBackground" :background="terminalBackground"
:font-size="terminalFontSize" :font-size="terminalFontSize"
@ -165,8 +171,8 @@ import Sftp from './sftp.vue'
import InputCommand from '@/components/input-command/index.vue' import InputCommand from '@/components/input-command/index.vue'
import HostForm from '../../server/components/host-form.vue' import HostForm from '../../server/components/host-form.vue'
import TerminalSetting from './terminal-setting.vue' import TerminalSetting from './terminal-setting.vue'
// import { randomStr } from '@utils/index.js'
import themeList from 'xterm-theme' import themeList from 'xterm-theme'
import { terminalStatusList } from '@/utils/enum'
const { proxy: { $nextTick, $store, $message } } = getCurrentInstance() const { proxy: { $nextTick, $store, $message } } = getCurrentInstance()
@ -226,6 +232,10 @@ onBeforeUnmount(() => {
window.removeEventListener('resize', handleResizeTerminalSftp) window.removeEventListener('resize', handleResizeTerminalSftp)
}) })
const getStatusColor = (status) => {
return terminalStatusList.find(item => item.value === status)?.color || 'gray'
}
const handleUpdateList = async ({ host }) => { const handleUpdateList = async ({ host }) => {
try { try {
await $store.getHostList() await $store.getHostList()
@ -461,6 +471,19 @@ const handleInputCommand = async (command) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; 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 { .tab_content_wrap {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -57,6 +57,8 @@ import { useRoute } from 'vue-router'
import Terminal from './components/terminal.vue' import Terminal from './components/terminal.vue'
import HostForm from '../server/components/host-form.vue' import HostForm from '../server/components/host-form.vue'
import { randomStr } from '@utils/index.js' import { randomStr } from '@utils/index.js'
import { terminalStatus } from '@/utils/enum'
const { CONNECTING } = terminalStatus
const { proxy: { $store, $message } } = getCurrentInstance() const { proxy: { $store, $message } } = getCurrentInstance()
@ -74,7 +76,7 @@ let isAllConfssh = computed(() => {
function linkTerminal(row) { function linkTerminal(row) {
const { name, host } = row const { name, host } = row
terminalTabs.push({ key: randomStr(16), name, host }) terminalTabs.push({ key: randomStr(16), name, host, status: CONNECTING })
} }
function handleUpdateHost(row) { function handleUpdateHost(row) {
@ -103,7 +105,7 @@ onActivated(async () => {
if (!host) return if (!host) return
let targetHosts = hostList.value.filter(item => host.includes(item.host)).map(item => { let targetHosts = hostList.value.filter(item => host.includes(item.host)).map(item => {
const { name, host } = item const { name, host } = item
return { key: randomStr(16), name, host } return { key: randomStr(16), name, host, status: CONNECTING }
}) })
if (!targetHosts || !targetHosts.length) return if (!targetHosts || !targetHosts.length) return
terminalTabs.push(...targetHosts) terminalTabs.push(...targetHosts)