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

This commit is contained in:
2026-03-19 18:13:24 +08:00
parent cd16ec17ae
commit cf19956b88
24 changed files with 5000 additions and 807 deletions
+54 -20
View File
@@ -65,9 +65,12 @@
<el-table-column label="大小" width="90">
<template #default="{ row }">{{ row.size ? formatSize(row.size) : '-' }}</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleGoDetail(row)">编辑</el-button>
<el-button link type="primary" @click="handleGoDetail(row)">详情</el-button>
<el-button link type="success" @click="handleSyncToHost(row)">同步</el-button>
<el-button link type="warning" @click="handleReloadMaster(row)">主控重下载</el-button>
<el-button link type="warning" @click="handleReloadOnHost(row)">宿主机重下载</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
@@ -218,6 +221,7 @@ import {
getImageList, getImageDetail, getImageHostStatus, createImage, updateImage, deleteImage,
reloadImage, syncImageToHost, reloadImageOnHost, getRemoteHostList
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
const route = useRoute()
const router = useRouter()
@@ -269,8 +273,8 @@ const formRules = {
path: [{ required: true, message: '请输入镜像路径', trigger: 'blur' }]
}
const statusType = (s) => ({ ready: 'success', downloading: 'warning', pending: 'info', error: 'danger' }[s] || 'info')
const statusLabel = (s) => ({ ready: '就绪', downloading: '下载中', pending: '等待中', error: '错误' }[s] || s || '-')
const statusType = (s) => ({ ready: 'success', success: 'success', downloading: 'warning', pending: 'info', error: 'danger', failed: 'danger', not_found: 'warning' }[s] || 'info')
const statusLabel = (s) => ({ ready: '就绪', success: '已同步', downloading: '下载中', pending: '等待中', error: '错误', failed: '失败', not_found: '未同步' }[s] || s || '-')
const formatSize = (bytes) => {
if (!bytes) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB']
@@ -298,6 +302,7 @@ const getHostName = (hid) => {
return h ? `${h.name}` : `#${hid}`
}
// 加载宿主机列表
const loadHostOptions = async () => {
try {
@@ -386,7 +391,7 @@ const handleSubmit = () => {
dialogVisible.value = false
loadList()
} else {
ElMessage.error(res?.data?.message || '操作失败')
ElMessage.error(extractApiError(res?.data, '操作失败'))
}
} catch (e) {
ElMessage.error('操作失败: ' + (e?.response?.data?.message || e.message))
@@ -396,30 +401,45 @@ const handleSubmit = () => {
})
}
const fetchHostStatusList = async (imageId) => {
if (!hostOptions.value.length) await loadHostOptions()
if (!hostOptions.value.length) return []
const results = await Promise.allSettled(
hostOptions.value.map(h =>
getImageHostStatus({ service_id: serviceId.value, image_id: imageId, host_id: h.id })
.then(res => {
const body = res?.data
if (body?.code === 200 && body?.data) {
const d = body.data
const img = d.image || {}
return { host_id: h.id, host_name: h.name || h.ip, status: d.status || 'not_found', path: img.path || '', image_name: img.name || '', image_status: img.status || '' }
}
return { host_id: h.id, host_name: h.name || h.ip, status: 'not_found' }
})
.catch(() => ({ host_id: h.id, host_name: h.name || h.ip, status: 'error' }))
)
)
return results.filter(r => r.status === 'fulfilled' && r.value).map(r => r.value)
}
const handleViewDetail = async (row) => {
detailVisible.value = true
detailLoading.value = true
currentDetail.value = row
hostStatusList.value = []
try {
const [detailRes, statusRes] = await Promise.allSettled([
const [detailRes, statusList] = await Promise.allSettled([
getImageDetail({ service_id: serviceId.value, image_id: row.id }),
getImageHostStatus({ service_id: serviceId.value, image_id: row.id })
fetchHostStatusList(row.id)
])
// 详情 - API 返回 data.image 嵌套
if (detailRes.status === 'fulfilled') {
const body = detailRes.value?.data
if (body?.code === 200 && body?.data) {
currentDetail.value = body.data.image ?? body.data.data ?? body.data
}
}
// 宿主机同步状态
if (statusRes.status === 'fulfilled') {
const body = statusRes.value?.data
if (body?.code === 200 && body?.data) {
const inner = body.data
hostStatusList.value = Array.isArray(inner) ? inner : (inner.hosts || inner.data || inner.list || [])
}
if (statusList.status === 'fulfilled') {
hostStatusList.value = statusList.value || []
}
} catch (e) {
console.error('获取镜像详情失败:', e)
@@ -449,7 +469,7 @@ const submitSyncToHost = async () => {
syncDialogVisible.value = false
loadList()
} else {
ElMessage.error(res?.data?.message || '同步失败')
ElMessage.error(extractApiError(res?.data, '同步失败'))
}
} catch (e) {
ElMessage.error('同步失败: ' + (e?.response?.data?.message || e.message))
@@ -458,7 +478,21 @@ const submitSyncToHost = async () => {
}
}
// 重下载镜像到宿主机
const handleReloadMaster = (row) => {
ElMessageBox.confirm(`确定要在主控端重新下载镜像「${row.name}」吗?`, '重新下载确认', {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
}).then(async () => {
try {
const fd = new FormData()
fd.append('service_id', serviceId.value)
fd.append('image_id', row.id)
const res = await reloadImage(fd)
if (res?.data?.code === 200) { ElMessage.success('已触发主控端重新下载'); loadList() }
else ElMessage.error(extractApiError(res?.data, '操作失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) }
}).catch(() => {})
}
const handleReloadOnHost = (row) => {
reloadTarget.value = row
reloadHostId.value = ''
@@ -479,7 +513,7 @@ const submitReloadOnHost = async () => {
reloadDialogVisible.value = false
loadList()
} else {
ElMessage.error(res?.data?.message || '操作失败')
ElMessage.error(extractApiError(res?.data, '操作失败'))
}
} catch (e) {
ElMessage.error('操作失败: ' + (e?.response?.data?.message || e.message))
@@ -499,8 +533,8 @@ const handleDelete = (row) => {
try {
const res = await deleteImage({ service_id: serviceId.value, image_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(() => {})
}