feat: 对接虚拟化平台管理
This commit is contained in:
@@ -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(() => {})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user