🐛 修复socket响应数据bug

This commit is contained in:
chaoszhu 2024-07-19 12:04:29 +08:00
parent db07d663e2
commit eaa5e5e65d
8 changed files with 360 additions and 316 deletions

View File

@ -9,7 +9,7 @@ module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
// parser: 'babel-eslint',
ecmaVersion: 2020,
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
'jsx': true

View File

@ -29,13 +29,24 @@ const useStore = defineStore({
sessionStorage.clear('token')
this.$patch({ token: null })
},
async getHostList() {
async getMainData() {
const { data: groupList } = await $api.getGroupList()
const { data: hostList } = await $api.getHostList()
// const { data: sshList } = await $api.getSshList()
// console.log('hostList:', hostList)
// console.log('groupList:', groupList)
this.$patch({ hostList, groupList })
},
async getHostList() {
const { data: hostList } = await $api.getHostList()
// console.log('hostList:', hostList)
this.$patch({ hostList })
},
async getGroupList() {
const { data: groupList } = await $api.getGroupList()
// console.log('groupList:', groupList)
this.$patch({ groupList })
},
getHostPing() {
setTimeout(() => {
this.hostList.forEach((item) => {

View File

@ -122,13 +122,12 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue'
import { ref, reactive, computed, getCurrentInstance } from 'vue'
const { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
const loading = ref(false)
const visible = ref(false)
const groupList = ref([])
const groupForm = reactive({
name: '',
index: ''
@ -167,15 +166,7 @@ const list = computed(() => {
})
})
const getGroupList = () => {
loading.value = true
$api.getGroupList()
.then(({ data }) => {
groupList.value = data
// groupForm.index = data.length
})
.finally(() => loading.value = false)
}
let groupList = computed(() => $store.groupList || [])
const addGroup = () => {
groupFormRef.value.validate()
@ -186,7 +177,8 @@ const addGroup = () => {
$message.success('success')
groupForm.name = ''
groupForm.index = ''
getGroupList()
$store.getGroupList()
groupFormRef.value.resetFields()
})
})
}
@ -206,7 +198,7 @@ const updateGroup = () => {
.then(() => {
$message.success('success')
visible.value = false
getGroupList()
$store.getGroupList()
})
})
}
@ -220,14 +212,11 @@ const deleteGroup = ({ id, name }) => {
.then(async () => {
await $api.deleteGroup(id)
await $store.getHostList()
await $store.getGroupList()
$message.success('success')
getGroupList()
})
}
onMounted(() => {
getGroupList()
})
</script>
<style lang="scss" scoped>

View File

@ -3,7 +3,7 @@
<AsideBox />
<div class="main_container">
<TopBar />
<router-view v-slot="{ Component }" class="router_box">
<router-view v-slot="{ Component }" v-loading="loading" class="router_box">
<keep-alive>
<component :is="Component" />
</keep-alive>
@ -13,8 +13,26 @@
</template>
<script setup>
import { ref, onBeforeMount, getCurrentInstance } from 'vue'
import AsideBox from '@/components/aside-box.vue'
import TopBar from '@/components/top-bar.vue'
const { proxy: { $store } } = getCurrentInstance()
const loading = ref(true)
const getMainData = async () => {
try {
loading.value = true
await $store.getMainData()
} finally {
loading.value = false
}
}
onBeforeMount(async () => {
await getMainData()
})
</script>
<style lang="scss" scoped>

View File

@ -113,9 +113,9 @@
</template>
<script setup>
import { ref, reactive, computed, watch, getCurrentInstance, nextTick } from 'vue'
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
const { proxy: { $api, $message } } = getCurrentInstance()
const { proxy: { $api, $message, $store } } = getCurrentInstance()
const props = defineProps({
show: {
@ -143,7 +143,6 @@ const resetForm = () => ({
const hostForm = reactive(resetForm())
const oldHost = ref('')
const groupList = ref([])
const rules = reactive({
group: { required: true, message: '选择一个分组' },
name: { required: true, message: '输入主机别名', trigger: 'change' },
@ -164,17 +163,7 @@ const visible = computed({
const title = computed(() => props.defaultData ? '修改服务器' : '新增服务器')
watch(() => props.show, (newVal) => {
if (!newVal) return
getGroupList()
})
const getGroupList = () => {
$api.getGroupList()
.then(({ data }) => {
groupList.value = data
})
}
let groupList = computed(() => $store.groupList || [])
const handleClosed = () => {
// console.log('handleClosed')
@ -185,7 +174,7 @@ const handleClosed = () => {
const setDefaultData = () => {
if (!props.defaultData) return
console.log(props.defaultData)
// console.log(props.defaultData)
let { name, host, index, expired, expiredNotify, consoleUrl, group, remark } = props.defaultData
oldHost.value = host
Object.assign(hostForm, { name, host, index, expired, expiredNotify, consoleUrl, group, remark })

View File

@ -8,7 +8,7 @@
</div>
<div class="server_group_collapse">
<el-collapse v-model="activeGroup">
<el-collapse-item v-for="(servers, groupName) in resList" :key="groupName" :name="groupName">
<el-collapse-item v-for="(servers, groupName) in groupHostList" :key="groupName" :name="groupName">
<template #title>
<div class="group_title">
{{ groupName }}
@ -37,40 +37,21 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, getCurrentInstance, reactive, computed, watch } from 'vue'
import { ref, onBeforeUnmount, getCurrentInstance, computed, watch, onMounted } from 'vue'
import { io } from 'socket.io-client'
import HostForm from './components/host-form.vue'
import Setting from './components/setting.vue'
import HostCard from './components/host-card.vue'
const { proxy: { $api, $store, $message, $notification, $router, $serviceURI } } = getCurrentInstance()
const { proxy: { $store, $notification, $router, $serviceURI, $message } } = getCurrentInstance()
const socket = ref(null)
const loading = ref(true)
const hostListStatus = ref([])
const updateHostData = ref(null)
const hostFormVisible = ref(false)
const settingVisible = ref(false)
const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0))
const activeGroup = ref([])
const handleLogout = () => {
$store.clearJwtToken()
$message({ type: 'success', message: '已安全退出', center: true })
$router.push('/login')
}
const getHostList = async () => {
try {
loading.value = true
await $store.getHostList()
connectIo()
} catch (err) {
loading.value = false
}
}
const connectIo = () => {
if (socket.value) socket.value.close()
let socketInstance = io($serviceURI, {
path: '/clients',
forceNew: true,
@ -80,16 +61,15 @@ const connectIo = () => {
socket.value = socketInstance
socketInstance.on('connect', () => {
let flag = 5
loading.value = false
console.log('clients websocket 已连接: ', socketInstance.id)
let token = $store.token
socketInstance.emit('init_clients_data', { token })
socketInstance.on('clients_data', (data) => {
if ((flag++ % 5) === 0) $store.getHostPing()
hostListStatus.value = $store.hostList.map(item => {
$store.hostList.forEach(item => {
const { host } = item
if (data[host] === null) return { ...item }
return Object.assign({}, item, data[host])
return Object.assign(item, data[host])
})
})
socketInstance.on('token_verify_fail', (message) => {
@ -105,14 +85,18 @@ const connectIo = () => {
console.error('clients websocket 连接断开')
})
socketInstance.on('connect_error', (message) => {
loading.value = false
console.error('clients websocket 连接出错: ', message)
})
}
const handleUpdateList = () => {
if (socket.value) socket.value.close()
getHostList()
const handleUpdateList = async () => {
try {
await $store.getHostList()
// connectIo()
} catch (err) {
$message.error('获取主机列表失败')
console.error('获取主机列表失败: ', err)
}
}
const handleUpdateHost = (defaultData) => {
@ -125,7 +109,7 @@ const handleHiddenIP = () => {
localStorage.setItem('hiddenIp', String(hiddenIp.value))
}
let resList = computed(() => {
let groupHostList = computed(() => {
let res = {}
let hostList = $store.hostList
let groupList = $store.groupList
@ -146,15 +130,15 @@ let resList = computed(() => {
return res
})
watch(resList, () => {
activeGroup.value = [...Object.keys(resList.value),]
watch(groupHostList, () => {
activeGroup.value = [...Object.keys(groupHostList.value),]
}, {
immediate: true,
deep: false
})
onMounted(async () => {
await getHostList()
onMounted(() => {
connectIo()
})
onBeforeUnmount(() => {

View File

@ -0,0 +1,264 @@
<template>
<div class="container">
<InfoSide
ref="infoSideRef"
v-model:show-input-command="showInputCommand"
:token="token"
:host="host"
:visible="visible"
@connect-sftp="connectSftp"
@click-input-command="clickInputCommand"
/>
<section>
<div class="terminals">
<el-button class="full-screen-button" type="success" @click="handleFullScreen">
{{ isFullScreen ? '退出全屏' : '全屏' }}
</el-button>
<div class="visible" @click="handleVisibleSidebar">
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
</div>
<el-tabs
v-model="activeTab"
type="border-card"
addable
tab-position="top"
@tab-remove="removeTab"
@tab-change="tabChange"
@tab-add="tabAdd"
>
<el-tab-pane
v-for="item in terminalTabs"
:key="item.key"
:label="item.title"
:name="item.key"
:closable="closable"
>
<TerminalTab
ref="terminalTabRefs"
:token="token"
:host="host"
:tab-key="item.key"
/>
</el-tab-pane>
</el-tabs>
</div>
<div v-if="showSftp" class="sftp">
<SftpFooter :token="token" :host="host" @resize="resizeTerminal" />
</div>
</section>
<InputCommand v-model:show="showInputCommand" @input-command="handleInputCommand" />
</div>
</template>
<script setup>
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
import TerminalTab from './components/terminal-tab.vue'
import InfoSide from './components/info-side.vue'
import SftpFooter from './components/sftp-footer.vue'
import InputCommand from '@/components/input-command/index.vue'
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
const name = ref('')
const host = ref('')
const token = $store.token
const activeTab = ref('')
const terminalTabs = reactive([])
const isFullScreen = ref(false)
const timer = ref(null)
const showSftp = ref(false)
const showInputCommand = ref(false)
const visible = ref(true)
const infoSideRef = ref(null)
const terminalTabRefs = ref([])
const closable = computed(() => terminalTabs.length > 1)
onBeforeMount(() => {
if (!token) return $router.push('login')
let { host: routeHost, name: routeName } = $route.query
name.value = routeName
host.value = routeHost
document.title = `${ document.title }-${ routeName }`
let key = Date.now().toString()
terminalTabs.push({ title: routeName, key })
activeTab.value = key
registryDbClick()
})
// const windowBeforeUnload = () => {
// window.onbeforeunload = () => {
// return ''
// }
// }
const connectSftp = (flag) => {
showSftp.value = flag
resizeTerminal()
}
const clickInputCommand = () => {
showInputCommand.value = true
}
const tabAdd = () => {
if (timer.value) clearTimeout(timer.value)
timer.value = setTimeout(() => {
let title = name.value
let key = Date.now().toString()
terminalTabs.push({ title, key })
activeTab.value = key
tabChange(key)
registryDbClick()
}, 200)
}
const removeTab = (removeKey) => {
let idx = terminalTabs.findIndex(({ key }) => removeKey === key)
terminalTabs.splice(idx, 1)
if (removeKey !== activeTab.value) return
activeTab.value = terminalTabs[0].key
}
const tabChange = async (key) => {
await $nextTick()
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => key === tabKey)
curTabTerminal?.focusTab()
}
const handleFullScreen = () => {
if (isFullScreen.value) document.exitFullscreen()
else document.getElementsByClassName('terminals')[0].requestFullscreen()
isFullScreen.value = !isFullScreen.value
}
const registryDbClick = () => {
$nextTick(() => {
let tabItems = Array.from(document.getElementsByClassName('el-tabs__item'))
tabItems.forEach(item => {
item.removeEventListener('dblclick', handleDblclick)
item.addEventListener('dblclick', handleDblclick)
})
})
}
const handleDblclick = (e) => {
if (terminalTabs.length > 1) {
let key = e.target.id.substring(4)
// console.log('dblclick', key)
removeTab(key)
}
}
const handleVisibleSidebar = () => {
visible.value = !visible.value
resizeTerminal()
}
const resizeTerminal = () => {
for (let terminalTabRef of terminalTabRefs.value) {
const { handleResize } = terminalTabRef || {}
handleResize && handleResize()
}
}
const handleInputCommand = async (command) => {
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTab.value === tabKey)
await $nextTick()
curTabTerminal?.focusTab()
curTabTerminal.handleInputCommand(`${ command }\n`)
showInputCommand.value = false
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
height: 100vh;
section {
flex: 1;
display: flex;
flex-direction: column;
width: calc(100vw - 250px); //
.terminals {
min-height: 150px;
flex: 1;
position: relative;
.full-screen-button {
position: absolute;
right: 10px;
top: 4px;
z-index: 99999;
}
}
.sftp {
border: 1px solid rgb(236, 215, 187);
}
.visible {
position: absolute;
z-index: 999999;
top: 13px;
left: 5px;
cursor: pointer;
transition: all 0.3s;
&:hover {
transform: scale(1.1);
}
}
}
}
</style>
<style lang="scss">
.el-tabs {
border: none;
}
.el-tabs--border-card>.el-tabs__content {
padding: 0;
}
.el-tabs__header {
position: sticky;
top: 0;
z-index: 1;
user-select: none;
}
// .el-tabs__nav-scroll {
// .el-tabs__nav {
// // padding-left: 60px;
// }
// }
.el-tabs__new-tab {
position: absolute;
left: 18px;
font-size: 50px;
z-index: 98;
}
// .el-tabs--border-card {
// height: 100%;
// overflow: hidden;
// display: flex;
// flex-direction: column;
// }
.el-tabs__content {
flex: 1;
}
.el-icon.is-icon-close {
font-size: 13px;
position: absolute;
right: 0px;
top: 2px;
}
</style>

View File

@ -1,264 +1,53 @@
<template>
<div class="container">
<InfoSide
ref="infoSideRef"
v-model:show-input-command="showInputCommand"
:token="token"
:host="host"
:visible="visible"
@connect-sftp="connectSftp"
@click-input-command="clickInputCommand"
/>
<section>
<div class="terminals">
<el-button class="full-screen-button" type="success" @click="handleFullScreen">
{{ isFullScreen ? '退出全屏' : '全屏' }}
</el-button>
<div class="visible" @click="handleVisibleSidebar">
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
</div>
<el-tabs
v-model="activeTab"
type="border-card"
addable
tab-position="top"
@tab-remove="removeTab"
@tab-change="tabChange"
@tab-add="tabAdd"
>
<el-tab-pane
v-for="item in terminalTabs"
:key="item.key"
:label="item.title"
:name="item.key"
:closable="closable"
>
<TerminalTab
ref="terminalTabRefs"
:token="token"
:host="host"
:tab-key="item.key"
/>
</el-tab-pane>
</el-tabs>
</div>
<div v-if="showSftp" class="sftp">
<SftpFooter :token="token" :host="host" @resize="resizeTerminal" />
</div>
</section>
<InputCommand v-model:show="showInputCommand" @input-command="handleInputCommand" />
<div class="terminal_container">
<div v-if="showLinkTips" class="terminal_link_tips">
<h2 class="quick_link_text">快速连接</h2>
<el-table
:data="hostList"
:show-header="false"
>
<el-table-column prop="name" label="name" width="180" />
<el-table-column prop="host" label="host" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
<!-- <ul v-for="item in hostList" :key="item._id">
{{ item.name }}
{{ item.host }}
{{ item.name }}
</ul> -->
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
import TerminalTab from './components/terminal-tab.vue'
import InfoSide from './components/info-side.vue'
import SftpFooter from './components/sftp-footer.vue'
import InputCommand from '@/components/input-command/index.vue'
import { ref, computed, getCurrentInstance } from 'vue'
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
const { proxy: { $store } } = getCurrentInstance()
const name = ref('')
const host = ref('')
const token = $store.token
const activeTab = ref('')
const terminalTabs = reactive([])
const isFullScreen = ref(false)
const timer = ref(null)
const showSftp = ref(false)
const showInputCommand = ref(false)
const visible = ref(true)
const infoSideRef = ref(null)
const terminalTabRefs = ref([])
let showLinkTips = ref(true)
const closable = computed(() => terminalTabs.length > 1)
let hostList = computed(() => $store.hostList)
onBeforeMount(() => {
if (!token) return $router.push('login')
let { host: routeHost, name: routeName } = $route.query
name.value = routeName
host.value = routeHost
document.title = `${ document.title }-${ routeName }`
let key = Date.now().toString()
terminalTabs.push({ title: routeName, key })
activeTab.value = key
registryDbClick()
})
// const windowBeforeUnload = () => {
// window.onbeforeunload = () => {
// return ''
// }
// }
const connectSftp = (flag) => {
showSftp.value = flag
resizeTerminal()
}
const clickInputCommand = () => {
showInputCommand.value = true
}
const tabAdd = () => {
if (timer.value) clearTimeout(timer.value)
timer.value = setTimeout(() => {
let title = name.value
let key = Date.now().toString()
terminalTabs.push({ title, key })
activeTab.value = key
tabChange(key)
registryDbClick()
}, 200)
}
const removeTab = (removeKey) => {
let idx = terminalTabs.findIndex(({ key }) => removeKey === key)
terminalTabs.splice(idx, 1)
if (removeKey !== activeTab.value) return
activeTab.value = terminalTabs[0].key
}
const tabChange = async (key) => {
await $nextTick()
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => key === tabKey)
curTabTerminal?.focusTab()
}
const handleFullScreen = () => {
if (isFullScreen.value) document.exitFullscreen()
else document.getElementsByClassName('terminals')[0].requestFullscreen()
isFullScreen.value = !isFullScreen.value
}
const registryDbClick = () => {
$nextTick(() => {
let tabItems = Array.from(document.getElementsByClassName('el-tabs__item'))
tabItems.forEach(item => {
item.removeEventListener('dblclick', handleDblclick)
item.addEventListener('dblclick', handleDblclick)
})
})
}
const handleDblclick = (e) => {
if (terminalTabs.length > 1) {
let key = e.target.id.substring(4)
// console.log('dblclick', key)
removeTab(key)
}
}
const handleVisibleSidebar = () => {
visible.value = !visible.value
resizeTerminal()
}
const resizeTerminal = () => {
for (let terminalTabRef of terminalTabRefs.value) {
const { handleResize } = terminalTabRef || {}
handleResize && handleResize()
}
}
const handleInputCommand = async (command) => {
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTab.value === tabKey)
await $nextTick()
curTabTerminal?.focusTab()
curTabTerminal.handleInputCommand(`${ command }\n`)
showInputCommand.value = false
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
height: 100vh;
section {
flex: 1;
.terminal_container {
.terminal_link_tips {
width: 50%;
// margin: 0 auto;
display: flex;
flex-direction: column;
width: calc(100vw - 250px); //
.terminals {
min-height: 150px;
flex: 1;
position: relative;
.full-screen-button {
position: absolute;
right: 10px;
top: 4px;
z-index: 99999;
}
}
.sftp {
border: 1px solid rgb(236, 215, 187);
}
.visible {
position: absolute;
z-index: 999999;
top: 13px;
left: 5px;
cursor: pointer;
transition: all 0.3s;
&:hover {
transform: scale(1.1);
}
justify-content: center;
align-items: center;
padding: 20px;
.quick_link_text {
align-self: self-start;
margin: 0 10px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
margin-bottom: 15px;
}
}
}
</style>
<style lang="scss">
.el-tabs {
border: none;
}
.el-tabs--border-card>.el-tabs__content {
padding: 0;
}
.el-tabs__header {
position: sticky;
top: 0;
z-index: 1;
user-select: none;
}
// .el-tabs__nav-scroll {
// .el-tabs__nav {
// // padding-left: 60px;
// }
// }
.el-tabs__new-tab {
position: absolute;
left: 18px;
font-size: 50px;
z-index: 98;
}
// .el-tabs--border-card {
// height: 100%;
// overflow: hidden;
// display: flex;
// flex-direction: column;
// }
.el-tabs__content {
flex: 1;
}
.el-icon.is-icon-close {
font-size: 13px;
position: absolute;
right: 0px;
top: 2px;
}
</style>