✨ 支持同主机名称任意端口
This commit is contained in:
parent
7e45186d22
commit
51b3c58673
@ -1,3 +1,12 @@
|
||||
## [2.3.0](https://github.com/chaos-zhu/easynode/releases) (2024-10-xx)
|
||||
|
||||
### Features
|
||||
|
||||
* 不再对同IP:PORT的实例进行校验
|
||||
* 支持同IP任意端口的服务器录入
|
||||
* 支持关闭所有终端连接
|
||||
* TODO: 兼容移动端展示
|
||||
|
||||
## [2.2.4](https://github.com/chaos-zhu/easynode/releases) (2024-08-31)
|
||||
|
||||
### Features
|
||||
|
@ -24,17 +24,16 @@ async function addHost({
|
||||
}) {
|
||||
let {
|
||||
body: {
|
||||
name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||
port, username, authType, password, privateKey, credential, command, tempKey
|
||||
name, host, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||
port: newPort, username, authType, password, privateKey, credential, command, tempKey
|
||||
}
|
||||
} = request
|
||||
// console.log(request)
|
||||
if (!newHost || !name) return res.fail({ msg: 'missing params: name or host' })
|
||||
if (!host || !name) return res.fail({ msg: 'missing params: name or host' })
|
||||
let hostList = await readHostList()
|
||||
if (hostList?.some(({ host }) => host === newHost)) return res.fail({ msg: `主机${ newHost }已存在` })
|
||||
let record = {
|
||||
name, host: newHost, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||
port, username, authType, password, privateKey, credential, command
|
||||
name, host, index, expired, expiredNotify, group, consoleUrl, remark,
|
||||
port: newPort, username, authType, password, privateKey, credential, command
|
||||
}
|
||||
if (record[authType]) {
|
||||
const clearTempKey = await RSADecryptSync(tempKey)
|
||||
@ -53,6 +52,7 @@ async function updateHost({ res, request }) {
|
||||
let {
|
||||
body: {
|
||||
hosts,
|
||||
id,
|
||||
host: newHost, name: newName, index, oldHost, expired, expiredNotify, group, consoleUrl, remark,
|
||||
port, username, authType, password, privateKey, credential, command, tempKey
|
||||
}
|
||||
@ -61,10 +61,9 @@ async function updateHost({ res, request }) {
|
||||
if (isBatch) {
|
||||
if (!hosts.length) return res.fail({ msg: 'hosts为空' })
|
||||
let hostList = await readHostList()
|
||||
// console.log('批量修改实例')
|
||||
let newHostList = []
|
||||
for (let oldRecord of hostList) {
|
||||
let record = hosts.find(item => item.host === oldRecord.host)
|
||||
let record = hosts.find(item => item.id === oldRecord._id)
|
||||
if (!record) {
|
||||
newHostList.push(oldRecord)
|
||||
continue
|
||||
@ -94,7 +93,7 @@ async function updateHost({ res, request }) {
|
||||
}
|
||||
if (!hostList.some(({ host }) => host === oldHost)) return res.fail({ msg: `原实例[${ oldHost }]不存在,请尝试添加实例` })
|
||||
|
||||
let idx = hostList.findIndex(({ host }) => host === oldHost)
|
||||
let idx = hostList.findIndex(({ _id }) => _id === id)
|
||||
const oldRecord = hostList[idx]
|
||||
// 如果存在原认证方式则保存下来
|
||||
if (!record[authType] && oldRecord[authType]) {
|
||||
@ -115,16 +114,10 @@ async function updateHost({ res, request }) {
|
||||
async function removeHost({
|
||||
res, request
|
||||
}) {
|
||||
let { body: { host } } = request
|
||||
let { body: { ids } } = request
|
||||
let hostList = await readHostList()
|
||||
if (Array.isArray(host)) {
|
||||
hostList = hostList.filter(item => !host.includes(item.host))
|
||||
// if (hostList.length === 0) return res.fail({ msg: '没有可删除的实例' })
|
||||
} else {
|
||||
let hostIdx = hostList.findIndex(item => item.host === host)
|
||||
if (hostIdx === -1) return res.fail({ msg: `${ host }不存在` })
|
||||
hostList.splice(hostIdx, 1)
|
||||
}
|
||||
if (!Array.isArray(ids)) return res.fail({ msg: '参数错误' })
|
||||
hostList = hostList.filter(({ id }) => !ids.includes(id))
|
||||
writeHostList(hostList)
|
||||
res.success({ data: '已移除' })
|
||||
}
|
||||
@ -135,9 +128,9 @@ async function importHost({
|
||||
let { body: { importHost, isEasyNodeJson = false } } = request
|
||||
if (!Array.isArray(importHost)) return res.fail({ msg: '参数错误' })
|
||||
let hostList = await readHostList()
|
||||
// 过滤已存在的host
|
||||
let hostListSet = new Set(hostList.map(item => item.host))
|
||||
let newHostList = importHost.filter(item => !hostListSet.has(item.host))
|
||||
// 考虑到批量导入可能会重复太多,先过滤已存在的host:port
|
||||
let hostListSet = new Set(hostList.map(({ host, port }) => `${ host }:${ port }`))
|
||||
let newHostList = importHost.filter(({ host, port }) => !hostListSet.has(`${ host }:${ port }`))
|
||||
let newHostListLen = newHostList.length
|
||||
if (newHostListLen === 0) return res.fail({ msg: '导入的实例已存在' })
|
||||
|
||||
@ -159,7 +152,6 @@ async function importHost({
|
||||
item.index = newHostListLen - index
|
||||
return Object.assign(item, { ...extraFiels })
|
||||
})
|
||||
|
||||
}
|
||||
hostList.push(...newHostList)
|
||||
writeHostList(hostList)
|
||||
|
@ -104,7 +104,7 @@ module.exports = (httpServer) => {
|
||||
return
|
||||
}
|
||||
isExecuting = true
|
||||
socket.on('create', async ({ hosts, token, command, timeout }) => {
|
||||
socket.on('create', async ({ hostIds, token, command, timeout }) => {
|
||||
const { code } = await verifyAuthSync(token, requestIP)
|
||||
if (code !== 1) {
|
||||
socket.emit('token_verify_fail')
|
||||
@ -126,13 +126,13 @@ module.exports = (httpServer) => {
|
||||
socket.disconnect()
|
||||
disconnectAllExecClient()
|
||||
}, timeout * 1000)
|
||||
console.log('hosts:', hosts)
|
||||
console.log('hostIds:', hostIds)
|
||||
// console.log('token:', token)
|
||||
console.log('command:', command)
|
||||
const hostList = await readHostList()
|
||||
const targetHostsInfo = hostList.filter(item => hosts.some(ip => item.host === ip)) || {}
|
||||
const targetHostsInfo = hostList.filter(item => hostIds.some(id => item._id === id)) || {}
|
||||
// console.log('targetHostsInfo:', targetHostsInfo)
|
||||
if (!targetHostsInfo.length) return socket.emit('create_fail', `未找到【${ hosts }】服务器信息`)
|
||||
if (!targetHostsInfo.length) return socket.emit('create_fail', `未找到【${ hostIds }】服务器信息`)
|
||||
// 查找 hostInfo -> 并发执行
|
||||
socket.emit('ready')
|
||||
let execPromise = targetHostsInfo.map((hostInfo, index) => {
|
||||
@ -141,7 +141,7 @@ module.exports = (httpServer) => {
|
||||
setTimeout(() => reject('执行超时'), timeout * 1000)
|
||||
let { authType, host, port, username } = hostInfo
|
||||
let authInfo = { host, port, username }
|
||||
let curRes = { command, host, name: hostInfo.name, result: '', status: execStatusEnum.connecting, date: Date.now() - (targetHostsInfo.length - index) } // , execStatusEnum
|
||||
let curRes = { command, host, port, name: hostInfo.name, result: '', status: execStatusEnum.connecting, date: Date.now() - (targetHostsInfo.length - index) } // , execStatusEnum
|
||||
execResult.push(curRes)
|
||||
try {
|
||||
if (authType === 'credential') {
|
||||
|
@ -34,8 +34,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="实例">
|
||||
<template #default="{ row }">
|
||||
<span style="letter-spacing: 2px;"> {{ row.name }} </span>
|
||||
<span style="letter-spacing: 2px;"> {{ row.host }} </span>
|
||||
<span style="letter-spacing: 2px;"> {{ row.name }} </span> -
|
||||
<span style="letter-spacing: 2px;"> {{ row.host }} </span> :
|
||||
<span style="letter-spacing: 2px;"> {{ row.port }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="command" label="指令" show-overflow-tooltip>
|
||||
@ -81,10 +82,10 @@
|
||||
label-width="80px"
|
||||
:show-message="false"
|
||||
>
|
||||
<el-form-item label="实例" prop="hosts">
|
||||
<el-form-item label="实例" prop="hostIds">
|
||||
<div class="select_host_wrap">
|
||||
<el-select
|
||||
v-model="formData.hosts"
|
||||
v-model="formData.hostIds"
|
||||
:teleported="false"
|
||||
multiple
|
||||
placeholder=""
|
||||
@ -105,7 +106,7 @@
|
||||
v-for="item in hasConfigHostList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.host"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<!-- <el-button type="primary" class="btn" @click="selectAllHost">全选</el-button> -->
|
||||
@ -182,7 +183,7 @@ let timeRemaining = ref(0)
|
||||
const isClient = ref(false)
|
||||
|
||||
let formData = reactive({
|
||||
hosts: [],
|
||||
hostIds: [],
|
||||
command: '',
|
||||
timeout: 120
|
||||
})
|
||||
@ -206,13 +207,13 @@ const expandRows = computed(() => {
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
hosts: { required: true, trigger: 'change' },
|
||||
hostIds: { required: true, trigger: 'change' },
|
||||
command: { required: true, trigger: 'change' },
|
||||
timeout: { required: true, type: 'number', trigger: 'change' }
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => formData.hosts, (val) => {
|
||||
watch(() => formData.hostIds, (val) => {
|
||||
if (val.length === 0) {
|
||||
checkAll.value = false
|
||||
indeterminate.value = false
|
||||
@ -224,7 +225,7 @@ watch(() => formData.hosts, (val) => {
|
||||
}
|
||||
})
|
||||
|
||||
const createExecShell = (hosts = [], command = 'ls', timeout = 60) => {
|
||||
const createExecShell = (hostIds = [], command = 'ls', timeout = 60) => {
|
||||
loading.value = true
|
||||
timeRemaining.value = Number(formData.timeout)
|
||||
let timer = null
|
||||
@ -243,7 +244,7 @@ const createExecShell = (hosts = [], command = 'ls', timeout = 60) => {
|
||||
pendingRecord.value = [] // 每轮执行前清空
|
||||
})
|
||||
|
||||
socket.value.emit('create', { hosts, token: token.value, command, timeout })
|
||||
socket.value.emit('create', { hostIds, token: token.value, command, timeout })
|
||||
|
||||
socket.value.on('output', (result) => {
|
||||
loading.value = false
|
||||
@ -320,9 +321,9 @@ onMounted(async () => {
|
||||
let selectAllHost = (val) => {
|
||||
indeterminate.value = false
|
||||
if (val) {
|
||||
formData.hosts = hasConfigHostList.value.map(item => item.host)
|
||||
formData.hostIds = hasConfigHostList.value.map(item => item.id)
|
||||
} else {
|
||||
formData.hosts = []
|
||||
formData.hostIds = []
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,16 +367,16 @@ let addOnekey = () => {
|
||||
function execOnekey() {
|
||||
updateFormRef.value.validate()
|
||||
.then(async () => {
|
||||
let { hosts, command, timeout } = formData
|
||||
let { hostIds, command, timeout } = formData
|
||||
timeout = Number(timeout)
|
||||
if (timeout < 1) {
|
||||
return $message.error('超时时间不能小于1秒')
|
||||
}
|
||||
if (hosts.length === 0) {
|
||||
if (hostIds.length === 0) {
|
||||
return $message.error('请选择主机')
|
||||
}
|
||||
await getOnekeyRecord() // 获取新纪录前会清空 pendingRecord,所以需要获取一次最新的list
|
||||
createExecShell(hosts, command, timeout)
|
||||
createExecShell(hostIds, command, timeout)
|
||||
formVisible.value = false
|
||||
})
|
||||
}
|
||||
@ -407,11 +408,12 @@ const handleRemoveAll = async () => {
|
||||
|
||||
onActivated(async () => {
|
||||
await nextTick()
|
||||
const { host, execClientInstallScript } = route.query
|
||||
if (!host) return
|
||||
const { hostIds, execClientInstallScript } = route.query
|
||||
if (!hostIds) return
|
||||
if (execClientInstallScript === 'true') {
|
||||
let clientInstallScript = 'curl -o- https://mirror.ghproxy.com/https://raw.githubusercontent.com/chaos-zhu/easynode/main/client/easynode-client-install.sh | bash\n'
|
||||
createExecShell(host.split(','), clientInstallScript, 300)
|
||||
console.log(hostIds.split(','))
|
||||
createExecShell(hostIds.split(','), clientInstallScript, 300)
|
||||
// $messageBox.confirm(`准备安装客户端服务监控应用:${ host }`, 'Warning', {
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
|
@ -1,392 +0,0 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="host-card">
|
||||
<div class="host-state">
|
||||
<span v-if="isError" class="offline">未连接</span>
|
||||
<span v-else class="online">已连接</span>
|
||||
<!-- {{ ping }} -->
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="weizhi field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-fuwuqi" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>系统</h2>
|
||||
<h3><span>名称:</span> {{ osInfo.hostname }}</h3>
|
||||
<h3><span>类型:</span> {{ osInfo.type }}</h3>
|
||||
<h3><span>架构:</span> {{ osInfo.arch }}</h3>
|
||||
<h3><span>平台:</span> {{ osInfo.platform }}</h3>
|
||||
<h3><span>版本:</span> {{ osInfo.release }}</h3>
|
||||
<h3><span>开机时长:</span> {{ $tools.formatTime(osInfo.uptime) }}</h3>
|
||||
<h3><span>到期时间:</span> {{ expiredTime }}</h3>
|
||||
<h3><span>本地IP:</span> {{ osInfo.ip }}</h3>
|
||||
<h3><span>连接数:</span> {{ openedCount || 0 }}</h3>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span class="name" @click="handleUpdate">
|
||||
{{ name || '--' }}
|
||||
<svg-icon name="icon-xiugai" class="svg-icon" />
|
||||
</span>
|
||||
<span>{{ osInfo?.type || '--' }}</span>
|
||||
<!-- <span>{{ osInfo?.hostname || '--' }}</span> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="weizhi field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-position" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>位置信息</h2>
|
||||
<h3><span>详细:</span> {{ ipInfo.country || '--' }} {{ ipInfo.regionName }}</h3>
|
||||
<!-- <h3><span>详细:</span> {{ ipInfo.country || '--' }} {{ ipInfo.regionName }} {{ ipInfo.city }}</h3> -->
|
||||
<!-- <h3><span>IP:</span> {{ hostIp }}</h3> -->
|
||||
<h3><span>提供商:</span> {{ ipInfo.isp || '--' }}</h3>
|
||||
<h3><span>线路:</span> {{ ipInfo.as || '--' }}</h3>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span>{{ `${ipInfo?.country || '--'} ${ipInfo?.regionName || '--'}` }}</span>
|
||||
<!-- <span>{{ `${ipInfo?.country || '--' } ${ipInfo?.regionName || '--'} ${ipInfo?.city || '--'}` }}</span> -->
|
||||
<span>{{ hostIp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cpu field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-xingzhuang" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>CPU</h2>
|
||||
<h3><span>利用率:</span> {{ cpuInfo.cpuUsage }}%</h3>
|
||||
<h3><span>物理核心:</span> {{ cpuInfo.cpuCount }}</h3>
|
||||
<h3><span>型号:</span> {{ cpuInfo.cpuModel }}</h3>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span :style="{ color: setColor(cpuInfo.cpuUsage) }">{{ cpuInfo.cpuUsage || '0' || '--' }}%</span>
|
||||
<span>{{ cpuInfo.cpuCount || '--' }} 核心</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ram field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-neicun1" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>内存</h2>
|
||||
<h3><span>总大小:</span> {{ $tools.toFixed(memInfo.totalMemMb / 1024) }} GB</h3>
|
||||
<h3><span>已使用:</span> {{ $tools.toFixed(memInfo.usedMemMb / 1024) }} GB</h3>
|
||||
<h3><span>占比:</span> {{ $tools.toFixed(memInfo.usedMemPercentage) }}%</h3>
|
||||
<h3><span>空闲:</span> {{ $tools.toFixed(memInfo.freeMemMb / 1024) }} GB</h3>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span :style="{ color: setColor(memInfo.usedMemPercentage) }">{{ $tools.toFixed(memInfo.usedMemPercentage)
|
||||
}}%</span>
|
||||
<span>{{ $tools.toFixed(memInfo.usedMemMb / 1024) }} | {{ $tools.toFixed(memInfo.totalMemMb / 1024) }} GB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yingpan field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-xingzhuang1" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>存储</h2>
|
||||
<h3><span>总空间:</span> {{ driveInfo.totalGb || '--' }} GB</h3>
|
||||
<h3><span>已使用:</span> {{ driveInfo.usedGb || '--' }} GB</h3>
|
||||
<h3><span>剩余:</span> {{ driveInfo.freeGb || '--' }} GB</h3>
|
||||
<h3><span>占比:</span> {{ driveInfo.usedPercentage || '--' }}%</h3>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span :style="{ color: setColor(driveInfo.usedPercentage) }">{{ driveInfo.usedPercentage || '--' }}%</span>
|
||||
<span>{{ driveInfo.usedGb || '--' }} | {{ driveInfo.totalGb || '--' }} GB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wangluo field">
|
||||
<el-popover placement="bottom-start" :width="200" trigger="hover">
|
||||
<template #reference>
|
||||
<svg-icon name="icon-wangluo1" class="svg-icon" />
|
||||
</template>
|
||||
<div class="field-detail">
|
||||
<h2>网卡</h2>
|
||||
<!-- <h3>
|
||||
<span>实时流量</span>
|
||||
<div>↑ {{ $tools.toFixed(netstatInfo.netTotal?.outputMb) || 0 }}MB / s</div>
|
||||
<div>↓ {{ $tools.toFixed(netstatInfo.netTotal?.inputMb) || 0 }}MB / s</div>
|
||||
</h3> -->
|
||||
<div v-for="(value, key) in netstatInfo.netCards" :key="key" style="display: flex; flex-direction: column;">
|
||||
<h3>
|
||||
<span>{{ key }}</span>
|
||||
<div>↑ {{ $tools.formatNetSpeed(value?.outputMb) || 0 }}</div>
|
||||
<div>↓ {{ $tools.formatNetSpeed(value?.inputMb) || 0 }}</div>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="fields">
|
||||
<span>↑ {{ $tools.formatNetSpeed(netstatInfo.netTotal?.outputMb) || 0 }}</span>
|
||||
<span>↓ {{ $tools.formatNetSpeed(netstatInfo.netTotal?.inputMb) || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field actions">
|
||||
<svg-icon
|
||||
name="icon-zhongduanguanli24"
|
||||
title="终端"
|
||||
class="actions-icon"
|
||||
@click="handleSSH"
|
||||
/>
|
||||
<svg-icon
|
||||
v-show="consoleUrl"
|
||||
name="icon-a-zu391"
|
||||
title="服务商控制台"
|
||||
class="actions-icon"
|
||||
@click="handleToConsole"
|
||||
/>
|
||||
<svg-icon
|
||||
name="icon-bianji1"
|
||||
title="编辑"
|
||||
class="actions-icon"
|
||||
@click="handleUpdate"
|
||||
/>
|
||||
<svg-icon
|
||||
name="icon-shanchu1"
|
||||
title="删除"
|
||||
class="actions-icon"
|
||||
@click="handleRemoveHost"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, getCurrentInstance } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const { proxy: { $api, $router, $tools } } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
hostInfo: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
hiddenIp: {
|
||||
required: true,
|
||||
type: [Number, Boolean,]
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update-list', 'update-host',])
|
||||
|
||||
const hostIp = computed(() => {
|
||||
let ip = hostInfo.value?.ipInfo?.query || hostInfo.value?.host || '--'
|
||||
try {
|
||||
let formatIp = ip.replace(/\d/g, '*').split('.').map((item) => item.padStart(3, '*')).join('.')
|
||||
return props.hiddenIp ? formatIp : ip
|
||||
} catch (error) {
|
||||
return ip
|
||||
}
|
||||
})
|
||||
|
||||
const hostInfo = computed(() => props.hostInfo || {})
|
||||
const host = computed(() => hostInfo.value?.host)
|
||||
const name = computed(() => hostInfo.value?.name)
|
||||
const ping = computed(() => hostInfo.value?.ping || '')
|
||||
const expiredTime = computed(() => $tools.formatTimestamp(hostInfo.value?.expired, 'date'))
|
||||
const consoleUrl = computed(() => hostInfo.value?.consoleUrl)
|
||||
const ipInfo = computed(() => hostInfo.value?.ipInfo || {})
|
||||
const isError = computed(() => !Boolean(hostInfo.value?.osInfo))
|
||||
const cpuInfo = computed(() => hostInfo.value?.cpuInfo || {})
|
||||
const memInfo = computed(() => hostInfo.value?.memInfo || {})
|
||||
const osInfo = computed(() => hostInfo.value?.osInfo || {})
|
||||
const driveInfo = computed(() => hostInfo.value?.driveInfo || {})
|
||||
const netstatInfo = computed(() => {
|
||||
let { total: netTotal, ...netCards } = hostInfo.value?.netstatInfo || {}
|
||||
return { netTotal, netCards: netCards || {} }
|
||||
})
|
||||
const openedCount = computed(() => hostInfo.value?.openedCount || 0)
|
||||
|
||||
const setColor = (num) => {
|
||||
num = Number(num)
|
||||
return num ? (num < 80 ? '#595959' : (num >= 80 && num < 90 ? '#FF6600' : '#FF0000')) : '#595959'
|
||||
}
|
||||
|
||||
const handleUpdate = () => {
|
||||
emit('update-host', hostInfo.value)
|
||||
}
|
||||
|
||||
const handleToConsole = () => {
|
||||
window.open(consoleUrl.value)
|
||||
}
|
||||
|
||||
const handleSSH = async () => {
|
||||
if(!hostInfo.value?.isConfig) {
|
||||
ElMessage({
|
||||
message: '请先配置SSH连接信息',
|
||||
type: 'warning',
|
||||
center: true
|
||||
})
|
||||
handleUpdate()
|
||||
return
|
||||
}
|
||||
$router.push({ path: '/terminal', query: { host: host.value } })
|
||||
}
|
||||
|
||||
const handleRemoveHost = async () => {
|
||||
ElMessageBox.confirm('确认删除实例', 'Warning', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let { data } = await $api.removeHost({ host: host.value })
|
||||
ElMessage({
|
||||
message: data,
|
||||
type: 'success',
|
||||
center: true
|
||||
})
|
||||
emit('update-list')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.host-card {
|
||||
margin: 0px 30px 20px;
|
||||
transition: all 0.5s;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 15px rgba(6, 30, 37, 0.5);
|
||||
}
|
||||
|
||||
.host-state {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
span {
|
||||
font-size: 10px;
|
||||
// transform: rotate(-45deg);
|
||||
// transform: scale(0.95);
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: #009933;
|
||||
background-color: #e8fff3;
|
||||
}
|
||||
|
||||
.offline {
|
||||
color: #FF0033;
|
||||
background-color: #fff5f8;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
|
||||
&>div {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
.field {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.svg-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
color: #1989fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// justify-content: center;
|
||||
span {
|
||||
padding: 3px 0;
|
||||
margin-left: 5px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
height: 19px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-color: #1989fa;
|
||||
|
||||
.svg-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
display: none;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
.actions-icon {
|
||||
margin: 0 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #1989fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.web-ssh {
|
||||
|
||||
// ::v-deep has been deprecated. Use :deep(<inner-selector>) instead.
|
||||
:deep(.el-dropdown__caret-button) {
|
||||
margin-left: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.field-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin: 0px 0 8px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span {
|
||||
font-weight: 600;
|
||||
color: #797979;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -3,7 +3,7 @@
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="hosts"
|
||||
row-key="host"
|
||||
row-key="id"
|
||||
:default-sort="defaultSort"
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@ -128,12 +128,12 @@ const handleToConsole = ({ consoleUrl }) => {
|
||||
}
|
||||
|
||||
const handleSSH = async (row) => {
|
||||
let { host } = row
|
||||
$router.push({ path: '/terminal', query: { host } })
|
||||
let { id } = row
|
||||
$router.push({ path: '/terminal', query: { hostIds: id } })
|
||||
}
|
||||
|
||||
const handleOnekey = async (row) => {
|
||||
let { host, isConfig } = row
|
||||
let { id, isConfig } = row
|
||||
if (!isConfig) {
|
||||
$message({
|
||||
message: '请先配置SSH连接信息',
|
||||
@ -143,7 +143,7 @@ const handleOnekey = async (row) => {
|
||||
handleUpdate(row)
|
||||
return
|
||||
}
|
||||
$router.push({ path: '/onekey', query: { host, execClientInstallScript: 'true' } })
|
||||
$router.push({ path: '/onekey', query: { hostIds: id, execClientInstallScript: 'true' } })
|
||||
}
|
||||
|
||||
let defaultSortLocal = localStorage.getItem('host_table_sort')
|
||||
@ -175,13 +175,13 @@ defineExpose({
|
||||
clearSelection
|
||||
})
|
||||
|
||||
const handleRemoveHost = async ({ host }) => {
|
||||
const handleRemoveHost = async ({ id }) => {
|
||||
$messageBox.confirm('确认删除实例', 'Warning', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let { data } = await $api.removeHost({ host })
|
||||
let { data } = await $api.removeHost({ ids: [id,] })
|
||||
$message({
|
||||
message: data,
|
||||
type: 'success',
|
||||
|
@ -121,10 +121,10 @@ let collectSelectHost = () => {
|
||||
let handleBatchSSH = () => {
|
||||
collectSelectHost()
|
||||
if (!selectHosts.value.length) return $message.warning('请选择要批量操作的实例')
|
||||
let ips = selectHosts.value.filter(item => item.isConfig).map(item => item.host)
|
||||
if (!ips.length) return $message.warning('所选实例未配置ssh连接信息')
|
||||
if (ips.length < selectHosts.value.length) $message.warning('部分实例未配置ssh连接信息,已忽略')
|
||||
$router.push({ path: '/terminal', query: { host: ips.join(',') } })
|
||||
let ids = selectHosts.value.filter(item => item.isConfig).map(item => item.id)
|
||||
if (!ids.length) return $message.warning('所选实例未配置ssh连接信息')
|
||||
if (ids.length < selectHosts.value.length) $message.warning('部分实例未配置ssh连接信息,已忽略')
|
||||
$router.push({ path: '/terminal', query: { hostIds: ids.join(',') } })
|
||||
}
|
||||
|
||||
let handleBatchModify = async () => {
|
||||
@ -137,7 +137,7 @@ let handleBatchModify = async () => {
|
||||
let handleBatchRemove = async () => {
|
||||
collectSelectHost()
|
||||
if (!selectHosts.value.length) return $message.warning('请选择要批量操作的实例')
|
||||
let ips = selectHosts.value.map(item => item.host)
|
||||
let ids = selectHosts.value.map(item => item.id)
|
||||
let names = selectHosts.value.map(item => item.name)
|
||||
|
||||
$messageBox.confirm(() => h('p', { style: 'line-height: 18px;' }, `确认删除\n${ names.join(', ') }吗?`), 'Warning', {
|
||||
@ -145,7 +145,7 @@ let handleBatchRemove = async () => {
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
let { data } = await $api.removeHost({ host: ips })
|
||||
let { data } = await $api.removeHost({ ids })
|
||||
$message({ message: data, type: 'success', center: true })
|
||||
selectHosts.value = []
|
||||
await handleUpdateList()
|
||||
@ -161,8 +161,8 @@ let handleUpdateHost = (defaultData) => {
|
||||
let handleBatchOnekey = async () => {
|
||||
collectSelectHost()
|
||||
if (!selectHosts.value.length) return $message.warning('请选择要批量操作的实例')
|
||||
let ips = selectHosts.value.map(item => item.host).join(',')
|
||||
$router.push({ path: '/onekey', query: { host: ips, execClientInstallScript: 'true' } })
|
||||
let ids = selectHosts.value.map(item => item.id).join(',')
|
||||
$router.push({ path: '/onekey', query: { hostIds: ids, execClientInstallScript: 'true' } })
|
||||
}
|
||||
|
||||
let handleBatchExport = () => {
|
||||
|
@ -101,9 +101,9 @@ const handleUpdateList = async ({ host }) => {
|
||||
|
||||
onActivated(async () => {
|
||||
await nextTick()
|
||||
const { host } = route.query
|
||||
if (!host) return
|
||||
let targetHosts = hostList.value.filter(item => host.includes(item.host)).map(item => {
|
||||
const { hostIds } = route.query
|
||||
if (!hostIds) return
|
||||
let targetHosts = hostList.value.filter(item => hostIds.includes(item.id)).map(item => {
|
||||
const { name, host } = item
|
||||
return { key: randomStr(16), name, host, status: CONNECTING }
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user