fix: 数据迁移模块
This commit is contained in:
@@ -1611,7 +1611,9 @@ const handleDetailMigrateState = () => {
|
|||||||
loadDataMigrateProgress()
|
loadDataMigrateProgress()
|
||||||
startMigratePolling()
|
startMigratePolling()
|
||||||
}
|
}
|
||||||
|
startDetailAutoRefresh()
|
||||||
} else if (!vm.migrating) {
|
} else if (!vm.migrating) {
|
||||||
|
stopDetailAutoRefresh()
|
||||||
if (migratePollingTimer) {
|
if (migratePollingTimer) {
|
||||||
stopMigratePolling()
|
stopMigratePolling()
|
||||||
dataMigrateProgressData.value = null
|
dataMigrateProgressData.value = null
|
||||||
@@ -2287,12 +2289,21 @@ const migrateProgressBarStatus = (stage) => {
|
|||||||
let migratePollingTimer = null
|
let migratePollingTimer = null
|
||||||
const startMigratePolling = () => {
|
const startMigratePolling = () => {
|
||||||
stopMigratePolling()
|
stopMigratePolling()
|
||||||
migratePollingTimer = setInterval(loadDataMigrateProgress, 5000)
|
migratePollingTimer = setInterval(loadDataMigrateProgress, 3000)
|
||||||
}
|
}
|
||||||
const stopMigratePolling = () => {
|
const stopMigratePolling = () => {
|
||||||
if (migratePollingTimer) { clearInterval(migratePollingTimer); migratePollingTimer = null }
|
if (migratePollingTimer) { clearInterval(migratePollingTimer); migratePollingTimer = null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let detailAutoRefreshTimer = null
|
||||||
|
const startDetailAutoRefresh = () => {
|
||||||
|
if (detailAutoRefreshTimer) return
|
||||||
|
detailAutoRefreshTimer = setInterval(() => { loadDetail() }, 3000)
|
||||||
|
}
|
||||||
|
const stopDetailAutoRefresh = () => {
|
||||||
|
if (detailAutoRefreshTimer) { clearInterval(detailAutoRefreshTimer); detailAutoRefreshTimer = null }
|
||||||
|
}
|
||||||
|
|
||||||
const abortLoading = ref(false)
|
const abortLoading = ref(false)
|
||||||
const handleAbortMigrate = () => {
|
const handleAbortMigrate = () => {
|
||||||
ElMessageBox.confirm('确定要中断当前数据迁移吗?此操作不可恢复!', '中断迁移', {
|
ElMessageBox.confirm('确定要中断当前数据迁移吗?此操作不可恢复!', '中断迁移', {
|
||||||
@@ -3361,7 +3372,7 @@ onActivated(() => {
|
|||||||
if (loadedVmId !== vmId.value) initPage()
|
if (loadedVmId !== vmId.value) initPage()
|
||||||
})
|
})
|
||||||
onDeactivated(() => { isPageActive = false; stopMigratePolling() })
|
onDeactivated(() => { isPageActive = false; stopMigratePolling() })
|
||||||
onBeforeUnmount(() => { isPageActive = false; disposeCharts(); stopMigratePolling() })
|
onBeforeUnmount(() => { isPageActive = false; disposeCharts(); stopMigratePolling(); stopDetailAutoRefresh() })
|
||||||
onMounted(() => { isPageActive = true; initPage() })
|
onMounted(() => { isPageActive = true; initPage() })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,21 @@
|
|||||||
<span v-else class="text-muted">-</span>
|
<span v-else class="text-muted">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" width="140">
|
<el-table-column label="状态" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="vmStatusType(row.status)" size="small">{{ vmStatusLabel(row.status) }}</el-tag>
|
<template v-if="row.migrating">
|
||||||
<el-tag v-if="row.data_migrate_status && !['completed','failed','aborted','cancelled'].includes(row.data_migrate_status)" type="warning" size="small" effect="dark" style="margin-left:4px">迁移中</el-tag>
|
<div class="migrate-inline-status">
|
||||||
|
<span class="migrate-inline-label">迁移中</span>
|
||||||
|
<el-progress
|
||||||
|
v-if="migrateProgressMap[row.id] != null"
|
||||||
|
:percentage="Math.min(migrateProgressMap[row.id], 100)"
|
||||||
|
:stroke-width="6" :show-text="false" status="warning"
|
||||||
|
style="flex:1;min-width:50px"
|
||||||
|
/>
|
||||||
|
<span class="migrate-inline-pct" v-if="migrateProgressMap[row.id] != null">{{ migrateProgressMap[row.id] }}%</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-tag v-else :type="vmStatusType(row.status)" size="small">{{ vmStatusLabel(row.status) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<!-- <el-table-column label="宿主机" width="140">
|
<!-- <el-table-column label="宿主机" width="140">
|
||||||
@@ -357,14 +368,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, inject, onMounted } from 'vue'
|
import { ref, reactive, computed, inject, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Refresh, Search, ArrowLeft, ArrowDown, WarningFilled } from '@element-plus/icons-vue'
|
import { Plus, Refresh, Search, ArrowLeft, ArrowDown, WarningFilled } from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
getRemoteHostList, getVmList, getVmDetail, getVmStatus,
|
getRemoteHostList, getVmList, getVmDetail, getVmStatus,
|
||||||
createVm, rebuildVm, startVm, stopVm, rebootVm, suspendVm,
|
createVm, rebuildVm, startVm, stopVm, rebootVm, suspendVm,
|
||||||
resumeVm, rescueVm, exitRescueVm, deleteVm, getNetworkList, getMetricsHistory
|
resumeVm, rescueVm, exitRescueVm, deleteVm, getNetworkList, getMetricsHistory,
|
||||||
|
getDataMigrateProgress
|
||||||
} from '@/api/admin/kvmService'
|
} from '@/api/admin/kvmService'
|
||||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||||
import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue'
|
import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue'
|
||||||
@@ -581,9 +593,46 @@ const loadList = async () => {
|
|||||||
vmList.value = inner.data || inner.vms || (Array.isArray(inner) ? inner : [])
|
vmList.value = inner.data || inner.vms || (Array.isArray(inner) ? inner : [])
|
||||||
total.value = inner.meta?.count ?? inner.all_count ?? inner.total ?? vmList.value.length
|
total.value = inner.meta?.count ?? inner.all_count ?? inner.total ?? vmList.value.length
|
||||||
} else { vmList.value = []; total.value = 0 }
|
} else { vmList.value = []; total.value = 0 }
|
||||||
|
handleMigrateAutoRefresh()
|
||||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取虚拟机列表失败')) } finally { loading.value = false }
|
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取虚拟机列表失败')) } finally { loading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const migrateProgressMap = reactive({})
|
||||||
|
let migrateAutoRefreshTimer = null
|
||||||
|
|
||||||
|
const fetchMigrateProgress = async () => {
|
||||||
|
const migratingVms = vmList.value.filter(v => v.migrating && v.migrate_task_id)
|
||||||
|
for (const vm of migratingVms) {
|
||||||
|
try {
|
||||||
|
const res = await getDataMigrateProgress({
|
||||||
|
service_id: serviceId.value,
|
||||||
|
migration_id: vm.migrate_task_id
|
||||||
|
})
|
||||||
|
if (res?.data?.code === 200 && res.data.data) {
|
||||||
|
migrateProgressMap[vm.id] = res.data.data.progress ?? null
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMigrateAutoRefresh = () => {
|
||||||
|
const hasMigrating = vmList.value.some(v => v.migrating)
|
||||||
|
if (hasMigrating) {
|
||||||
|
fetchMigrateProgress()
|
||||||
|
if (!migrateAutoRefreshTimer) {
|
||||||
|
migrateAutoRefreshTimer = setInterval(() => { loadList() }, 3000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopMigrateAutoRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopMigrateAutoRefresh = () => {
|
||||||
|
if (migrateAutoRefreshTimer) { clearInterval(migrateAutoRefreshTimer); migrateAutoRefreshTimer = null }
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => stopMigrateAutoRefresh())
|
||||||
|
|
||||||
const handleSearch = () => { queryParams.page = 1; loadList() }
|
const handleSearch = () => { queryParams.page = 1; loadList() }
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
@@ -820,4 +869,7 @@ defineExpose({ loadList })
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.vm-manage-container { padding: 20px; }
|
.vm-manage-container { padding: 20px; }
|
||||||
.vm-config { display: flex; gap: 4px; flex-wrap: wrap; }
|
.vm-config { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
|
.migrate-inline-status { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
|
||||||
|
.migrate-inline-label { color: #e6a23c; font-size: 13px; font-weight: 600; white-space: nowrap; }
|
||||||
|
.migrate-inline-pct { color: #e6a23c; font-size: 12px; white-space: nowrap; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user