feat: 对接虚拟化平台管理
Build and Deploy Vue3 / build (push) Successful in 1m22s
Build and Deploy Vue3 / deploy (push) Successful in 1m2s

This commit is contained in:
lin
2026-03-19 18:13:24 +08:00
parent cd16ec17ae
commit cf19956b88
24 changed files with 5000 additions and 807 deletions
+39 -14
View File
@@ -39,12 +39,18 @@
<span class="host-addr">{{ row.ip || '-' }}:{{ row.port || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="Token" min-width="160" show-overflow-tooltip>
<el-table-column label="Token" min-width="140" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.token" class="token-mask">{{ maskToken(row.token) }}</span>
<span v-else class="text-muted">未设置</span>
</template>
</el-table-column>
<el-table-column label="延迟" width="100">
<template #default="{ row }">
<span v-if="row._delay != null" :style="{ color: row._delay > 500000000 ? '#F56C6C' : row._delay > 100000000 ? '#E6A23C' : '#67C23A' }">{{ formatDelay(row._delay) }}</span>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
@@ -118,12 +124,10 @@
</el-form>
<div v-if="vmVncResult" class="vnc-result">
<el-descriptions :column="1" border size="small">
<el-descriptions-item v-for="(val, key) in vmVncResult" :key="key" :label="key">
<template v-if="typeof val === 'string' && (val.startsWith('http') || val.startsWith('ws'))">
<el-link type="primary" :href="val" target="_blank">{{ val }}</el-link>
</template>
<template v-else>{{ val }}</template>
<el-descriptions-item label="VNC地址">
<el-link type="primary" :href="vmVncResult.url" target="_blank">{{ vmVncResult.url }}</el-link>
</el-descriptions-item>
<el-descriptions-item label="过期时间">{{ formatTimestamp(vmVncResult.expire_at) }}</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
@@ -145,6 +149,7 @@ import {
getVncNodeList, getVmVnc, addVncNode, testVncNode, updateVncNode, deleteVncNode,
getRemoteHostList
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import VmSelectorPopup from '@/components/admin/VmSelectorPopup.vue'
const route = useRoute()
@@ -169,6 +174,20 @@ const maskToken = (token) => {
return token.substring(0, 4) + '****' + token.substring(token.length - 4)
}
const formatTimestamp = (ts) => {
if (!ts) return '-'
if (typeof ts === 'object' && ts.seconds) return new Date(Number(ts.seconds) * 1000).toLocaleString('zh-CN')
if (typeof ts === 'string' || typeof ts === 'number') { const d = new Date(ts); return isNaN(d.getTime()) ? String(ts) : d.toLocaleString('zh-CN') }
return '-'
}
const formatDelay = (ns) => {
if (!ns && ns !== 0) return '-'
const ms = ns / 1000000
if (ms >= 1000) return (ms / 1000).toFixed(2) + ' s'
return ms.toFixed(2) + ' ms'
}
const loadHostOptions = async () => {
try {
const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 100 })
@@ -241,8 +260,8 @@ const handleSubmit = () => {
ElMessage.success(formType.value === 'add' ? '创建成功' : '修改成功')
formDialogVisible.value = false
loadList()
} else ElMessage.error(res?.data?.message || '操作失败')
} catch (e) { ElMessage.error('操作失败') } finally { submitLoading.value = false }
} else ElMessage.error(extractApiError(res?.data, '操作失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { submitLoading.value = false }
})
}
@@ -253,8 +272,8 @@ const handleDelete = (row) => {
try {
const res = await deleteVncNode({ service_id: serviceId.value, id: row.id })
if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadList() }
else ElMessage.error(res?.data?.message || '删除失败')
} catch (e) { ElMessage.error('删除失败') }
else ElMessage.error(extractApiError(res?.data, '删除失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
}).catch(() => {})
}
@@ -279,9 +298,15 @@ const submitTest = async () => {
try {
const res = await testVncNode({ service_id: serviceId.value, id: testTarget.value.id, host_id: testHostId.value })
if (res?.data?.code === 200) {
testResult.value = { success: true, message: '连接测试成功' }
const d = res.data.data || {}
const delay = d.delay
if (delay != null && testTarget.value) {
const idx = nodeList.value.findIndex(n => n.id === testTarget.value.id)
if (idx >= 0) nodeList.value[idx]._delay = delay
}
testResult.value = { success: true, message: '连接测试成功' + (delay != null ? `,延迟:${formatDelay(delay)}` : '') }
} else {
testResult.value = { success: false, message: res?.data?.message || '连接测试失败' }
testResult.value = { success: false, message: extractApiError(res?.data, '连接测试失败') }
}
} catch (e) {
testResult.value = { success: false, message: '连接测试异常: ' + (e?.message || '未知错误') }
@@ -315,9 +340,9 @@ const submitVmVnc = async () => {
if (res?.data?.code === 200 && res?.data?.data) {
vmVncResult.value = res.data.data
} else {
ElMessage.error(res?.data?.message || '获取VNC连接信息失败')
ElMessage.error(extractApiError(res?.data, '获取VNC连接信息失败'))
}
} catch (e) { ElMessage.error('获取失败') } finally { vmVncLoading.value = false }
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取VNC连接信息失败')) } finally { vmVncLoading.value = false }
}
const goBack = () => { router.push('/virtualization/kvm-service') }