取消
@@ -47,10 +52,21 @@ const list = ref([])
const selectedItem = ref(null)
const hostIdFilter = ref('')
const hostOptions = ref([])
+const keyword = ref('')
+const page = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
watch(() => props.modelValue, (val) => {
visible.value = val
- if (val) { loadHostOptions(); if (props.hostId) { hostIdFilter.value = props.hostId; loadList() } }
+ if (val) {
+ selectedItem.value = null
+ hostIdFilter.value = props.hostId || ''
+ keyword.value = ''
+ page.value = 1
+ loadHostOptions()
+ loadList()
+ }
})
watch(visible, (val) => emit('update:modelValue', val))
@@ -61,26 +77,31 @@ const loadHostOptions = async () => {
if (body?.code === 200 && body?.data) {
const inner = body.data
hostOptions.value = inner.hosts || inner.data || (Array.isArray(inner) ? inner : [])
- if (!hostIdFilter.value && hostOptions.value.length) hostIdFilter.value = hostOptions.value[0].id
- if (hostIdFilter.value) loadList()
}
} catch { /* ignore */ }
}
const loadList = async () => {
- if (!hostIdFilter.value) return
+ if (!props.serviceId) return
loading.value = true
try {
- const res = await getVmList({ service_id: props.serviceId, host_id: hostIdFilter.value, page: 1, count: 10 })
+ const params = { service_id: props.serviceId, page: page.value, count: pageSize.value }
+ if (hostIdFilter.value) params.host_id = hostIdFilter.value
+ if (keyword.value) params.key = keyword.value
+ const res = await getVmList(params)
const body = res?.data
if (body?.code === 200 && body?.data) {
const inner = body.data
list.value = inner.data || (Array.isArray(inner) ? inner : [])
+ total.value = inner.meta?.count ?? inner.all_count ?? inner.total ?? list.value.length
}
- } catch { /* ignore */ }
+ } catch { list.value = []; total.value = 0 }
finally { loading.value = false }
}
+const handleSearch = () => { page.value = 1; loadList() }
+const handleSizeChange = () => { page.value = 1; loadList() }
+
const formatMem = (kb) => {
if (!kb) return '-'
if (kb >= 1048576) return (kb / 1048576).toFixed(1) + ' GB'
@@ -99,12 +120,13 @@ const handleConfirm = () => {
visible.value = false
}
}
-const handleClose = () => { selectedItem.value = null }
+const handleClose = () => { selectedItem.value = null; list.value = []; total.value = 0 }
diff --git a/src/router/index.js b/src/router/index.js
index 241e24b..656d932 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -556,14 +556,6 @@ const routes = [
title: 'VNC指令管理'
}
},
- {
- path: 'migration-record',
- name: 'VmMigrationRecord',
- component: () => import('../views/virtualization/VmMigrationRecord.vue'),
- meta: {
- title: '虚拟机迁移记录'
- }
- },
{
path: 'host-detail',
name: 'VirtHostDetail',
diff --git a/src/views/product/UserGoodsList.vue b/src/views/product/UserGoodsList.vue
index 56d1e23..548cc31 100644
--- a/src/views/product/UserGoodsList.vue
+++ b/src/views/product/UserGoodsList.vue
@@ -612,7 +612,7 @@ const loadList = async () => {
if (res?.data?.code === 200 && res?.data?.data) {
const d = res.data.data
list.value = d.data || (Array.isArray(d) ? d : [])
- total.value = d.all_count ?? d.total ?? list.value.length
+ total.value = d.meta?.count ?? d.all_count ?? d.total ?? list.value.length
} else { list.value = []; total.value = 0 }
} catch { list.value = []; total.value = 0 } finally { loading.value = false }
}
@@ -825,7 +825,7 @@ const loadVmListForItem = async () => {
if (res?.data?.code === 200 && res?.data?.data) {
const d = res.data.data
vmListForItem.value = d.data || (Array.isArray(d) ? d : [])
- vmListTotal.value = d.all_count ?? d.total ?? vmListForItem.value.length
+ vmListTotal.value = d.meta?.count ?? d.all_count ?? d.total ?? vmListForItem.value.length
}
} catch { vmListForItem.value = [] } finally { vmListLoading.value = false }
}
diff --git a/src/views/user-vm/UserVmDetail.vue b/src/views/user-vm/UserVmDetail.vue
index 86d986f..b060993 100644
--- a/src/views/user-vm/UserVmDetail.vue
+++ b/src/views/user-vm/UserVmDetail.vue
@@ -6,10 +6,7 @@
用户虚拟机详情
-
- 迁移记录
- 刷新
-
+ 刷新
diff --git a/src/views/user-vm/UserVmList.vue b/src/views/user-vm/UserVmList.vue
index 05fce08..f0a4e31 100644
--- a/src/views/user-vm/UserVmList.vue
+++ b/src/views/user-vm/UserVmList.vue
@@ -1519,7 +1519,7 @@ const loadVmListForItem = async () => {
if (res?.data?.code === 200 && res?.data?.data) {
const d = res.data.data
vmListForItem.value = d.data || (Array.isArray(d) ? d : [])
- vmListTotal.value = d.all_count ?? d.total ?? vmListForItem.value.length
+ vmListTotal.value = d.meta?.count ?? d.all_count ?? d.total ?? vmListForItem.value.length
}
} catch { vmListForItem.value = [] } finally { vmListLoading.value = false }
}
@@ -1710,7 +1710,7 @@ const loadBindVmList = async () => {
if (res?.data?.code === 200 && res?.data?.data) {
const d = res.data.data
bindVmList.value = d.data || (Array.isArray(d) ? d : [])
- bindVmTotal.value = d.all_count ?? d.total ?? bindVmList.value.length
+ bindVmTotal.value = d.meta?.count ?? d.all_count ?? d.total ?? bindVmList.value.length
} else { bindVmList.value = []; bindVmTotal.value = 0 }
} catch { bindVmList.value = [] } finally { bindVmLoading.value = false }
}
diff --git a/src/views/virtualization/KvmServiceDetail.vue b/src/views/virtualization/KvmServiceDetail.vue
index 5cfdfd4..2ba1f2e 100644
--- a/src/views/virtualization/KvmServiceDetail.vue
+++ b/src/views/virtualization/KvmServiceDetail.vue
@@ -107,6 +107,9 @@
+
+
+
@@ -161,6 +164,7 @@ const VncNodeManage = defineAsyncComponent(() => import('./VncNodeManage.vue'))
const SnapshotManage = defineAsyncComponent(() => import('./SnapshotManage.vue'))
const BackupManage = defineAsyncComponent(() => import('./BackupManage.vue'))
const UserNetworkingManage = defineAsyncComponent(() => import('./UserNetworkingManage.vue'))
+const VmMigrationRecord = defineAsyncComponent(() => import('./VmMigrationRecord.vue'))
// 引入tagsViewStore
import { useTagsViewStore } from '@/store/tagsViewStore'
@@ -193,7 +197,8 @@ const tabLoaded = reactive({
'vnc': false,
'snapshot': false,
'backup': false,
- 'networking': false
+ 'networking': false,
+ 'migration': false
})
@@ -347,8 +352,11 @@ const initPage = () => {
// 重置所有 tab 加载状态,使子组件在切换后重新拉取数据
Object.keys(tabLoaded).forEach(k => { tabLoaded[k] = false })
+ const queryTab = route.query.tab
const savedTab = localStorage.getItem('kvmDetailActiveTab')
- if (savedTab && tabLoaded.hasOwnProperty(savedTab)) {
+ if (queryTab && tabLoaded.hasOwnProperty(queryTab)) {
+ activeTab.value = queryTab
+ } else if (savedTab && tabLoaded.hasOwnProperty(savedTab)) {
activeTab.value = savedTab
} else {
activeTab.value = 'host'
@@ -583,4 +591,8 @@ onMounted(() => {
padding: 0;
}
+.custom-tabs :deep(.migration-record-page) {
+ padding: 0;
+}
+
diff --git a/src/views/virtualization/VmDetail.vue b/src/views/virtualization/VmDetail.vue
index aabae1d..5656580 100644
--- a/src/views/virtualization/VmDetail.vue
+++ b/src/views/virtualization/VmDetail.vue
@@ -1769,7 +1769,10 @@ const tagsViewStore = useTagsViewStore()
const serviceId = computed(() => parseInt(route.query.service_id) || 0)
const serviceName = computed(() => route.query.service_name || '')
const vmId = computed(() => parseInt(route.query.vm_id) || 0)
-const goMigrationRecords = (query = {}) => router.push({ path: '/virtualization/migration-record', query })
+const goMigrationRecords = (query = {}) => router.push({
+ path: '/virtualization/kvm-service-detail',
+ query: { service_id: serviceId.value, service_name: serviceName.value, tab: 'migration', ...query }
+})
const goVmById = (targetServiceId, targetVmId) => router.push({ path: '/virtualization/vm-detail', query: { service_id: targetServiceId, vm_id: targetVmId } })
const loading = ref(false)
@@ -1997,6 +2000,19 @@ const loadHostOptions = async () => {
} catch { /* */ }
}
+const readHostId = value => Number(value?.host_id ?? value?.hostId ?? value?.HostId ?? 0) || 0
+const resolveVmHostId = responseData => {
+ const vm = responseData?.data ?? responseData?.vm ?? responseData
+ const volumes = responseData?.volumes || []
+ const networks = responseData?.networks || []
+ const systemVolume = volumes.find(volume => (volume.is_system ?? volume.isSystem) && readHostId(volume))
+ const anyVolume = volumes.find(volume => readHostId(volume))
+ const anyNetwork = networks.find(network => readHostId(network))
+ return readHostId(responseData) || readHostId(responseData?.host) || readHostId(vm) ||
+ readHostId(systemVolume) || readHostId(anyVolume) || readHostId(anyNetwork) ||
+ Number(route.query.host_id) || 0
+}
+
const loadDetail = async () => {
if (!vmId.value) return
loading.value = true
@@ -2021,7 +2037,7 @@ const loadDetail = async () => {
}
vmPortGroup.value = d.in_port_group || null
vmOutPortGroup.value = d.out_port_group || null
- vmHostId.value = detail.value?.host_id || vmVolumes.value[0]?.host_id || vmNetworks.value[0]?.host_id || 0
+ vmHostId.value = resolveVmHostId(d)
handleDetailMigrateState()
} else ElMessage.error(extractApiError(res?.data, '加载失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '加载失败')) } finally { loading.value = false }
@@ -2159,6 +2175,10 @@ const vmMemPercent = (m) => {
const loadHistoricalMetrics = async () => {
if (!serviceId.value || !detail.value?.name) return
+ if (!vmHostId.value) {
+ ElMessage.warning('未获取到虚拟机所属宿主机,无法加载监控指标')
+ return
+ }
const range = getMonitorTimeRange()
if (!range) return
historicalMetricsLoading.value = true
diff --git a/src/views/virtualization/VmManage.vue b/src/views/virtualization/VmManage.vue
index 653c8bd..93ea5b0 100644
--- a/src/views/virtualization/VmManage.vue
+++ b/src/views/virtualization/VmManage.vue
@@ -987,6 +987,18 @@ const handleExitRescue = (row) => {
}).catch(() => {})
}
+const readHostId = value => Number(value?.host_id ?? value?.hostId ?? value?.HostId ?? 0) || 0
+const resolveVmHostId = responseData => {
+ const vm = responseData?.data ?? responseData?.vm ?? responseData
+ const volumes = responseData?.volumes || []
+ const networks = responseData?.networks || []
+ const systemVolume = volumes.find(volume => (volume.is_system ?? volume.isSystem) && readHostId(volume))
+ const anyVolume = volumes.find(volume => readHostId(volume))
+ const anyNetwork = networks.find(network => readHostId(network))
+ return readHostId(responseData) || readHostId(responseData?.host) || readHostId(vm) ||
+ readHostId(systemVolume) || readHostId(anyVolume) || readHostId(anyNetwork) || 0
+}
+
const handleViewDetail = async (row) => {
detailVisible.value = true
detailLoading.value = true
@@ -1001,6 +1013,7 @@ const handleViewDetail = async (row) => {
vm.volumes = d.volumes || []
vm.image = d.image || null
vm.in_port_group = d.in_port_group || null
+ vm.host_id = resolveVmHostId(d) || Number(hostId.value) || 0
if (!vm.ips && vm.networks?.length) vm.ips = vm.networks.map(n => n.address?.split('/')[0]).filter(Boolean).join(', ')
currentDetail.value = vm
}
@@ -1023,11 +1036,16 @@ const fetchVmStatus = async (vm) => {
const fetchVmMetrics = async (vm) => {
try {
+ const metricsHostId = readHostId(vm) || Number(hostId.value) || 0
+ if (!metricsHostId) {
+ ElMessage.warning('未获取到虚拟机所属宿主机,无法查看指标')
+ return
+ }
const now = new Date()
const start = new Date(now.getTime() - 60 * 60 * 1000)
const res = await getMetricsHistory({
service_id: serviceId.value,
- host_id: vm.host_id || hostId.value,
+ host_id: metricsHostId,
vm_name: vm.name,
start: start.toISOString(),
end_time: now.toISOString(),
diff --git a/src/views/virtualization/VmMigrationRecord.vue b/src/views/virtualization/VmMigrationRecord.vue
index 628cc04..2341424 100644
--- a/src/views/virtualization/VmMigrationRecord.vue
+++ b/src/views/virtualization/VmMigrationRecord.vue
@@ -10,6 +10,14 @@
+
+
+
+
+
+
+
+
@@ -23,8 +31,12 @@
-
-
+
+ 选择
+
+
+ 选择
+
@@ -84,21 +96,34 @@