支持同主机名称任意端口

This commit is contained in:
chaos-zhu 2024-10-11 22:51:02 +08:00
parent 7e45186d22
commit 51b3c58673
8 changed files with 66 additions and 455 deletions

View File

@ -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

View File

@ -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)

View File

@ -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') {

View File

@ -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() // pendingRecordlist
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: '',

View File

@ -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>

View File

@ -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',

View File

@ -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 = () => {

View File

@ -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 }
})