🆕 重构监控数据结构

This commit is contained in:
chaoszhu 2024-07-21 23:58:25 +08:00
parent 5b2b776155
commit 655e9bc8af
11 changed files with 132 additions and 169 deletions

View File

@ -1,7 +1,10 @@
const { readGroupList, writeGroupList, readHostList, writeHostList, randomStr } = require('../utils') const { readGroupList, writeGroupList, readHostList, writeHostList, randomStr } = require('../utils')
async function getGroupList({ res }) { async function getGroupList({ res }) {
const data = await readGroupList() let data = await readGroupList()
data = data.map(item => {
return { ...item, id: item._id }
})
data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0)) data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0))
res.success({ data }) res.success({ data })
} }

View File

@ -5,9 +5,11 @@ async function getHostList({ res }) {
let data = await readHostList() let data = await readHostList()
data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0)) data?.sort((a, b) => Number(b.index || 0) - Number(a.index || 0))
data = data.map((item) => { data = data.map((item) => {
const isConfig = Boolean(item.username && item.port && (item[item.authType])) const { username, port, authType, _id: id } = item
const isConfig = Boolean(username && port && (item[authType]))
return { return {
...item, ...item,
id,
isConfig, isConfig,
password: '', password: '',
privateKey: '' privateKey: ''

View File

@ -25,6 +25,7 @@ app.config.globalProperties.$store = useStore()
const serviceURI = import.meta.env.DEV ? process.env.serviceURI : location.origin const serviceURI = import.meta.env.DEV ? process.env.serviceURI : location.origin
app.config.globalProperties.$serviceURI = serviceURI app.config.globalProperties.$serviceURI = serviceURI
app.config.globalProperties.$clientPort = process.env.clientPort || 22022 app.config.globalProperties.$clientPort = process.env.clientPort || 22022
app.config.globalProperties.$store.$patch({ serviceURI })
console.warn('ISDEV: ', import.meta.env.DEV) console.warn('ISDEV: ', import.meta.env.DEV)
console.warn('serviceURI: ', serviceURI) console.warn('serviceURI: ', serviceURI)

View File

@ -5,8 +5,6 @@ import { createRouter, createWebHistory } from 'vue-router'
// import terminal from '@views/terminal/index.vue' // import terminal from '@views/terminal/index.vue'
// import test from '@views/test/index.vue' // import test from '@views/test/index.vue'
const HostList = () => import('@views/list/index.vue')
const Login = () => import('@views/login/index.vue') const Login = () => import('@views/login/index.vue')
const Container = () => import('@views/index.vue') const Container = () => import('@views/index.vue')
const Server = () => import('@views/server/index.vue') const Server = () => import('@views/server/index.vue')

View File

@ -1,3 +1,4 @@
import { io } from 'socket.io-client'
import { defineStore, acceptHMRUpdate } from 'pinia' import { defineStore, acceptHMRUpdate } from 'pinia'
import $api from '@/api' import $api from '@/api'
import ping from '@/utils/ping' import ping from '@/utils/ping'
@ -5,9 +6,11 @@ import ping from '@/utils/ping'
const useStore = defineStore({ const useStore = defineStore({
id: 'global', id: 'global',
state: () => ({ state: () => ({
serviceURI: null,
hostList: [], hostList: [],
groupList: [], groupList: [],
sshList: [], sshList: [],
HostStatusSocket: null,
user: localStorage.getItem('user') || null, user: localStorage.getItem('user') || null,
token: sessionStorage.getItem('token') || localStorage.getItem('token') || null, token: sessionStorage.getItem('token') || localStorage.getItem('token') || null,
title: '' title: ''
@ -31,18 +34,15 @@ const useStore = defineStore({
this.$patch({ token: null }) this.$patch({ token: null })
}, },
async getMainData() { async getMainData() {
const { data: groupList } = await $api.getGroupList() await this.getGroupList()
const { data: hostList } = await $api.getHostList() await this.getHostList()
const { data: sshList } = await $api.getSSHList() await this.getSSHList()
// console.log('hostList:', hostList)
// console.log('groupList:', groupList)
// console.log('sshList:', sshList)
this.$patch({ groupList, hostList, sshList })
}, },
async getHostList() { async getHostList() {
const { data: hostList } = await $api.getHostList() const { data: hostList } = await $api.getHostList()
// console.log('hostList:', hostList) // console.log('hostList:', hostList)
this.$patch({ hostList }) this.$patch({ hostList })
this.wsHostStatus()
}, },
async getGroupList() { async getGroupList() {
const { data: groupList } = await $api.getGroupList() const { data: groupList } = await $api.getGroupList()
@ -67,11 +67,39 @@ const useStore = defineStore({
// console.warn('Please tick \'Preserve Log\'') // console.warn('Please tick \'Preserve Log\'')
}, 1500) }, 1500)
}, },
async sortHostList(list) { async wsHostStatus() {
let hostList = list.map(({ host }) => { if (this.HostStatusSocket) this.HostStatusSocket.close()
return this.hostList.find(item => item.host === host) let socketInstance = io(this.serviceURI, {
path: '/clients',
forceNew: true,
reconnectionDelay: 5000,
reconnectionAttempts: 2
})
this.HostStatusSocket = socketInstance
socketInstance.on('connect', () => {
let flag = 5
console.log('clients websocket 已连接: ', socketInstance.id)
let token = this.token
socketInstance.emit('init_clients_data', { token })
socketInstance.on('clients_data', (data) => {
if ((flag++ % 5) === 0) this.getHostPing()
this.hostList.forEach(item => {
const { host } = item
if (data[host] === null) return { ...item }
return Object.assign(item, data[host])
})
})
socketInstance.on('token_verify_fail', (message) => {
console.log('token 验证失败:', message)
// $router.push('/login')
})
})
socketInstance.on('disconnect', () => {
console.error('clients websocket 连接断开')
})
socketInstance.on('connect_error', (message) => {
console.error('clients websocket 连接出错: ', message)
}) })
this.$patch({ hostList })
} }
} }
}) })

View File

@ -37,14 +37,12 @@
</template> </template>
<script setup> <script setup>
import { ref, onBeforeUnmount, getCurrentInstance, computed, watch, onMounted } from 'vue' import { ref, onBeforeUnmount, getCurrentInstance, computed, watch } from 'vue'
import { io } from 'socket.io-client'
import HostCard from './components/host-card.vue' import HostCard from './components/host-card.vue'
import HostForm from './components/host-form.vue' import HostForm from './components/host-form.vue'
const { proxy: { $store, $notification, $router, $serviceURI, $message } } = getCurrentInstance() const { proxy: { $store, $message } } = getCurrentInstance()
const socket = ref(null)
const updateHostData = ref(null) const updateHostData = ref(null)
const hostFormVisible = ref(false) const hostFormVisible = ref(false)
const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0)) const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0))
@ -53,7 +51,6 @@ const activeGroup = ref([])
const handleUpdateList = async () => { const handleUpdateList = async () => {
try { try {
await $store.getHostList() await $store.getHostList()
connectIo()
} catch (err) { } catch (err) {
$message.error('获取实例列表失败') $message.error('获取实例列表失败')
console.error('获取实例列表失败: ', err) console.error('获取实例列表失败: ', err)
@ -98,60 +95,6 @@ watch(groupHostList, () => {
deep: false deep: false
}) })
let hostList = computed(() => $store.hostList)
const unwatchHost = watch(hostList, () => {
connectIo()
})
const connectIo = () => {
if (socket.value) socket.value.close()
if (typeof(unwatchHost) === 'function') unwatchHost()
let socketInstance = io($serviceURI, {
path: '/clients',
forceNew: true,
reconnectionDelay: 5000,
reconnectionAttempts: 2
})
socket.value = socketInstance
socketInstance.on('connect', () => {
let flag = 5
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()
$store.hostList.forEach(item => {
const { host } = item
if (data[host] === null) return { ...item }
return Object.assign(item, data[host])
})
})
socketInstance.on('token_verify_fail', (message) => {
$notification({
title: '鉴权失败',
message,
type: 'error'
})
$router.push('/login')
})
})
socketInstance.on('disconnect', () => {
console.error('clients websocket 连接断开')
})
socketInstance.on('connect_error', (message) => {
console.error('clients websocket 连接出错: ', message)
})
}
// onMounted(() => {
// connectIo()
// })
onBeforeUnmount(() => {
if (socket.value) socket.value.close()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -11,7 +11,7 @@
/> />
</div> --> </div> -->
</header> </header>
<el-divider class="first-divider" content-position="center">POSITION</el-divider> <el-divider class="first-divider" content-position="center">地理位置</el-divider>
<el-descriptions <el-descriptions
class="margin-top" class="margin-top"
:column="1" :column="1"
@ -47,7 +47,7 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-divider content-position="center">INDICATOR</el-divider> <el-divider content-position="center">实时监控</el-divider>
<el-descriptions <el-descriptions
class="margin-top" class="margin-top"
:column="1" :column="1"
@ -118,7 +118,7 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-divider content-position="center">INFORMATION</el-divider> <el-divider content-position="center">系统信息</el-divider>
<el-descriptions <el-descriptions
class="margin-top" class="margin-top"
:column="1" :column="1"
@ -208,16 +208,12 @@
import { ref, onMounted, onBeforeUnmount, computed, getCurrentInstance } from 'vue' import { ref, onMounted, onBeforeUnmount, computed, getCurrentInstance } from 'vue'
import socketIo from 'socket.io-client' import socketIo from 'socket.io-client'
const { proxy: { $router, $serviceURI, $message, $notification, $tools } } = getCurrentInstance() const { proxy: { $router, $store, $serviceURI, $message, $notification, $tools } } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
token: { hostInfo: {
required: true, required: true,
type: String type: Object
},
host: {
required: true,
type: String
}, },
visible: { visible: {
required: true, required: true,
@ -233,11 +229,13 @@ const emit = defineEmits(['update:inputCommandStyle', 'connect-sftp', 'click-inp
const socket = ref(null) const socket = ref(null)
const name = ref('') const name = ref('')
const hostData = ref(null)
const ping = ref(0) const ping = ref(0)
const pingTimer = ref(null) const pingTimer = ref(null)
const sftpStatus = ref(false) const sftpStatus = ref(false)
const token = computed(() => $store.token)
const hostData = computed(() => props.hostInfo)
const host = computed(() => hostData.value.host)
const ipInfo = computed(() => hostData.value?.ipInfo || {}) const ipInfo = computed(() => hostData.value?.ipInfo || {})
// const isError = computed(() => !Boolean(hostData.value?.osInfo)) // const isError = computed(() => !Boolean(hostData.value?.osInfo))
const cpuInfo = computed(() => hostData.value?.cpuInfo || {}) const cpuInfo = computed(() => hostData.value?.cpuInfo || {})
@ -280,7 +278,6 @@ const clickInputCommand = () => {
} }
const connectIO = () => { const connectIO = () => {
const { host, token } = props
socket.value = socketIo($serviceURI, { socket.value = socketIo($serviceURI, {
path: '/host-status', path: '/host-status',
forceNew: true, forceNew: true,
@ -291,8 +288,8 @@ const connectIO = () => {
socket.value.on('connect', () => { socket.value.on('connect', () => {
console.log('/host-status socket已连接', socket.value.id) console.log('/host-status socket已连接', socket.value.id)
socket.value.emit('init_host_data', { token, host }) socket.value.emit('init_host_data', { token: token.value, host: props.host })
getHostPing() // getHostPing()
socket.value.on('host_data', (data) => { socket.value.on('host_data', (data) => {
if (!data) return hostData.value = null if (!data) return hostData.value = null
hostData.value = data hostData.value = data
@ -343,9 +340,9 @@ const getHostPing = () => {
} }
onMounted(() => { onMounted(() => {
name.value = $router.currentRoute.value.query.name || '' // name.value = $router.currentRoute.value.query.name || ''
if (!props.host || !name.value) return $message.error('参数错误') // if (!props.host || !name.value) return $message.error('')
connectIO() // connectIO()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -12,7 +12,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue' import { ref, onMounted, computed, onBeforeUnmount, getCurrentInstance } from 'vue'
import { Terminal } from 'xterm' import { Terminal } from 'xterm'
import 'xterm/css/xterm.css' import 'xterm/css/xterm.css'
import { FitAddon } from 'xterm-addon-fit' import { FitAddon } from 'xterm-addon-fit'
@ -22,20 +22,12 @@ import { WebLinksAddon } from 'xterm-addon-web-links'
import socketIo from 'socket.io-client' import socketIo from 'socket.io-client'
const { io } = socketIo const { io } = socketIo
const { proxy: { $api, $serviceURI, $notification, $router, $messageBox } } = getCurrentInstance() const { proxy: { $api, $store, $serviceURI, $notification, $router, $messageBox } } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
token: {
required: true,
type: String
},
host: { host: {
required: true, required: true,
type: String type: String
},
tabKey: {
required: true,
type: String
} }
}) })
@ -48,7 +40,7 @@ const searchBar = ref(null)
const isManual = ref(false) const isManual = ref(false)
const terminalRefs = ref(null) const terminalRefs = ref(null)
const tabKey = ref(props.tabKey) const token = computed(() => $store.token)
const getCommand = async () => { const getCommand = async () => {
let { data } = await $api.getCommand(props.host) let { data } = await $api.getCommand(props.host)
@ -56,7 +48,7 @@ const getCommand = async () => {
} }
const connectIO = () => { const connectIO = () => {
const { host, token } = props const { host } = props
socket.value = io($serviceURI, { socket.value = io($serviceURI, {
path: '/terminal', path: '/terminal',
forceNew: false, forceNew: false,
@ -65,7 +57,7 @@ const connectIO = () => {
socket.value.on('connect', () => { socket.value.on('connect', () => {
console.log('/terminal socket已连接', socket.value.id) console.log('/terminal socket已连接', socket.value.id)
socket.value.emit('create', { host, token }) socket.value.emit('create', { host, token: token.value })
socket.value.on('connect_success', () => { socket.value.on('connect_success', () => {
onData() onData()
socket.value.on('connect_terminal', () => { socket.value.on('connect_terminal', () => {
@ -241,7 +233,7 @@ const handleInputCommand = (command) => {
onMounted(async () => { onMounted(async () => {
createLocalTerminal() createLocalTerminal()
await getCommand() // await getCommand()
connectIO() connectIO()
}) })
@ -255,8 +247,7 @@ defineExpose({
focusTab, focusTab,
handleResize, handleResize,
handleInputCommand, handleInputCommand,
handleClear, handleClear
tabKey
}) })
</script> </script>

View File

@ -3,8 +3,7 @@
<InfoSide <InfoSide
ref="infoSideRef" ref="infoSideRef"
v-model:show-input-command="showInputCommand" v-model:show-input-command="showInputCommand"
:token="token" :host-info="curHost"
:host="host"
:visible="visible" :visible="visible"
@connect-sftp="connectSftp" @connect-sftp="connectSftp"
@click-input-command="clickInputCommand" @click-input-command="clickInputCommand"
@ -18,26 +17,23 @@
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" /> <svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
</div> </div>
<el-tabs <el-tabs
v-model="activeTab" v-model="activeTabIndex"
type="border-card" type="border-card"
addable addable
tab-position="top" tab-position="top"
@tab-remove="removeTab" @tab-remove="removeTab"
@tab-change="tabChange" @tab-change="tabChange"
@tab-add="tabAdd"
> >
<el-tab-pane <el-tab-pane
v-for="item in terminalTabs" v-for="(item, index) in terminalTabs"
:key="item.key" :key="index"
:label="item.title" :label="item.name"
:name="item.key" :name="index"
:closable="closable" :closable="true"
> >
<TerminalTab <TerminalTab
ref="terminalTabRefs" ref="terminalTabRefs"
:token="token" :host="item.host"
:host="host"
:tab-key="item.key"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -51,7 +47,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, onBeforeMount,defineProps, getCurrentInstance } from 'vue' import { ref, reactive, defineEmits, computed, onBeforeMount,defineProps, getCurrentInstance, watch } from 'vue'
import TerminalTab from './terminal-tab.vue' import TerminalTab from './terminal-tab.vue'
import InfoSide from './info-side.vue' import InfoSide from './info-side.vue'
import SftpFooter from './sftp-footer.vue' import SftpFooter from './sftp-footer.vue'
@ -60,16 +56,16 @@ import InputCommand from '@/components/input-command/index.vue'
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance() const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
const props = defineProps({ const props = defineProps({
ternimalTabs: { terminalTabs: {
type: Array, type: Array,
required: true required: true
} }
}) })
const name = ref('') const emit = defineEmits(['closed', 'removeTab',])
const host = ref('')
const activeTab = ref('') const activeTabIndex = ref(0)
const terminalTabs = reactive([]) // const terminalTabs = reactive([])
const isFullScreen = ref(false) const isFullScreen = ref(false)
const timer = ref(null) const timer = ref(null)
const showSftp = ref(false) const showSftp = ref(false)
@ -77,21 +73,24 @@ const showInputCommand = ref(false)
const visible = ref(true) const visible = ref(true)
const infoSideRef = ref(null) const infoSideRef = ref(null)
const terminalTabRefs = ref([]) const terminalTabRefs = ref([])
const token = computed(() => $store.token) const token = computed(() => $store.token)
const ternimalTabs = computed(() => props.ternimalTabs) const terminalTabs = computed(() => props.terminalTabs)
const curHost = computed(() => terminalTabs.value[activeTabIndex.value])
const closable = computed(() => terminalTabs.length > 1) // const closable = computed(() => terminalTabs.length > 1)
onBeforeMount(() => { watch(terminalTabs, () => {
if (!token.value) return $router.push('login') console.log('add tab:', terminalTabs.value)
let { host: routeHost, name: routeName } = $route.query let len = terminalTabs.value.length
name.value = routeName if (len > 0) {
host.value = routeHost activeTabIndex.value = len - 1
document.title = `${ document.title }-${ routeName }` // registryDbClick()
let key = Date.now().toString() // tabChange(terminalTabs.value[0].key)
terminalTabs.push({ title: routeName, key }) }
activeTab.value = key }, {
registryDbClick() immediate: true,
deep: false
}) })
// const windowBeforeUnload = () => { // const windowBeforeUnload = () => {
@ -114,23 +113,23 @@ const tabAdd = () => {
timer.value = setTimeout(() => { timer.value = setTimeout(() => {
let title = name.value let title = name.value
let key = Date.now().toString() let key = Date.now().toString()
terminalTabs.push({ title, key }) terminalTabs.value.push({ title, key })
activeTab.value = key activeTabIndex.value = key
tabChange(key) tabChange(key)
registryDbClick() // registryDbClick()
}, 200) }, 200)
} }
const removeTab = (removeKey) => { const removeTab = (index) => {
let idx = terminalTabs.findIndex(({ key }) => removeKey === key) // terminalTabs.value.splice(index, 1)
terminalTabs.splice(idx, 1) emit('removeTab', index)
if (removeKey !== activeTab.value) return if (index !== activeTabIndex.value) return
activeTab.value = terminalTabs[0].key activeTabIndex.value = 0
} }
const tabChange = async (key) => { const tabChange = async (index) => {
await $nextTick() await $nextTick()
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => key === tabKey) const curTabTerminal = terminalTabRefs.value[index]
curTabTerminal?.focusTab() curTabTerminal?.focusTab()
} }
@ -140,23 +139,20 @@ const handleFullScreen = () => {
isFullScreen.value = !isFullScreen.value isFullScreen.value = !isFullScreen.value
} }
const registryDbClick = () => { // const registryDbClick = () => {
$nextTick(() => { // $nextTick(() => {
let tabItems = Array.from(document.getElementsByClassName('el-tabs__item')) // let tabItems = Array.from(document.getElementsByClassName('el-tabs__item'))
tabItems.forEach(item => { // tabItems.forEach(item => {
item.removeEventListener('dblclick', handleDblclick) // item.removeEventListener('dblclick', handleDblclick)
item.addEventListener('dblclick', handleDblclick) // item.addEventListener('dblclick', handleDblclick)
}) // })
}) // })
} // }
const handleDblclick = (e) => { // const handleDblclick = (e) => {
if (terminalTabs.length > 1) { // let key = e.target.id.substring(4)
let key = e.target.id.substring(4) // removeTab(key)
// console.log('dblclick', key) // }
removeTab(key)
}
}
const handleVisibleSidebar = () => { const handleVisibleSidebar = () => {
visible.value = !visible.value visible.value = !visible.value
@ -171,7 +167,7 @@ const resizeTerminal = () => {
} }
const handleInputCommand = async (command) => { const handleInputCommand = async (command) => {
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTab.value === tabKey) const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTabIndex.value === tabKey)
await $nextTick() await $nextTick()
curTabTerminal?.focusTab() curTabTerminal?.focusTab()
curTabTerminal.handleInputCommand(`${ command }\n`) curTabTerminal.handleInputCommand(`${ command }\n`)

View File

@ -37,7 +37,7 @@
</el-table> </el-table>
</div> </div>
<div v-else> <div v-else>
<Terminal :ternimal-tabs="ternimalTabs" /> <Terminal :terminal-tabs="terminalTabs" @remove-tab="handleRemoveTab" />
</div> </div>
<HostForm <HostForm
v-model:show="hostFormVisible" v-model:show="hostFormVisible"
@ -49,17 +49,17 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onActivated, getCurrentInstance } from 'vue' import { ref, computed, onActivated, getCurrentInstance, reactive } from 'vue'
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'
const { proxy: { $store, $message } } = getCurrentInstance() const { proxy: { $store, $message } } = getCurrentInstance()
let ternimalTabs = ref([]) let terminalTabs = reactive([])
const hostFormVisible = ref(false) const hostFormVisible = ref(false)
const updateHostData = ref(null) const updateHostData = ref(null)
let showLinkTips = computed(() => !Boolean(ternimalTabs.value.length)) let showLinkTips = computed(() => !Boolean(terminalTabs.length))
let hostList = computed(() => $store.hostList) let hostList = computed(() => $store.hostList)
@ -69,7 +69,7 @@ let isAllConfssh = computed(() => {
function linkTerminal(row) { function linkTerminal(row) {
// console.log(row) // console.log(row)
ternimalTabs.value.push(row) terminalTabs.push(row)
} }
function handleUpdateHost(row) { function handleUpdateHost(row) {
@ -77,6 +77,10 @@ function handleUpdateHost(row) {
updateHostData.value = { ...row } updateHostData.value = { ...row }
} }
function handleRemoveTab(index) {
terminalTabs.splice(index, 1)
}
const handleUpdateList = async () => { const handleUpdateList = async () => {
try { try {
await $store.getHostList() await $store.getHostList()