fix: 将迁移记录移入主控服务详情 #30

Merged
shiran merged 4 commits from master into deploy 2026-08-04 09:34:56 +08:00
5 changed files with 30 additions and 19 deletions
Showing only changes of commit 7dccfa781b - Show all commits
-8
View File
@@ -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',
+1 -4
View File
@@ -6,10 +6,7 @@
<el-divider direction="vertical" />
<span class="page-title">用户虚拟机详情</span>
</div>
<div>
<el-button plain @click="router.push({ path: '/virtualization/migration-record', query: { user_goods_id: userGoodsId } })">迁移记录</el-button>
<el-button :icon="Refresh" plain @click="loadDetail" :loading="loading">刷新</el-button>
</div>
<el-button :icon="Refresh" plain @click="loadDetail" :loading="loading">刷新</el-button>
</div>
<div class="main-content" v-loading="loading">
+14 -2
View File
@@ -107,6 +107,9 @@
<el-tab-pane label="用户组网" name="networking">
<UserNetworkingManage v-if="tabLoaded['networking']" />
</el-tab-pane>
<el-tab-pane label="迁移记录" name="migration">
<VmMigrationRecord v-if="tabLoaded['migration']" />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
@@ -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;
}
</style>
+4 -1
View File
@@ -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)
+11 -4
View File
@@ -10,6 +10,9 @@
<el-card shadow="never" class="filter-card">
<el-form :inline="true" :model="filters">
<el-form-item label="当前服务方向" v-if="embedded && serviceId">
<el-segmented v-model="serviceDirection" :options="[{ label: '作为源端', value: 'source' }, { label: '作为目标端', value: 'target' }]" @change="search" />
</el-form-item>
<el-form-item label="迁移类型">
<el-select v-model="filters.migration_type" clearable placeholder="全部" style="width:150px">
<el-option label="宿主机迁移" value="host_migrate" />
@@ -88,7 +91,7 @@
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { computed, inject, onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Refresh, Search } from '@element-plus/icons-vue'
@@ -97,6 +100,10 @@ import { extractApiError } from '@/utils/kvmErrorUtil'
const router = useRouter()
const route = useRoute()
const embedded = inject('embedded', false)
const injectedServiceId = inject('serviceId', null)
const serviceId = computed(() => injectedServiceId?.value || Number(route.query.service_id) || 0)
const serviceDirection = ref('source')
const loading = ref(false), records = ref([]), total = ref(0), page = ref(1), count = ref(20)
const filters = reactive({ migration_type: '', status: '', chain_id: '', task_id: '', user_id: null, user_goods_id: null, started_range: [] })
const statusOptions = ['pending','exporting','importing','running','success','failed','aborted','warning'].map(value => ({ value, label: ({ pending:'等待中',exporting:'导出中',importing:'导入中',running:'执行中',success:'成功',failed:'失败',aborted:'已中断',warning:'警告' })[value] }))
@@ -106,7 +113,7 @@ const typeLabel = t => ({ host_migrate:'宿主机迁移', data_migrate:'跨服
const formatTime = value => { if (!value) return '-'; const d = new Date(value); return Number.isNaN(d.getTime()) ? String(value) : d.toLocaleString('zh-CN', { hour12:false }) }
const shortId = value => !value ? '-' : (value.length > 18 ? `${value.slice(0,8)}${value.slice(-6)}` : value)
const durationText = row => { if (!row.started_at) return '-'; const end = row.completed_at ? new Date(row.completed_at) : new Date(); const sec = Math.max(0, Math.floor((end-new Date(row.started_at))/1000)); return sec >= 3600 ? `${Math.floor(sec/3600)}${Math.floor(sec%3600/60)}` : sec >= 60 ? `${Math.floor(sec/60)}${sec%60}` : `${sec}` }
const buildParams = () => { const p = { page:page.value, count:count.value }; ['migration_type','status','chain_id','task_id','user_id','user_goods_id'].forEach(k => { if (filters[k] !== '' && filters[k] != null) p[k]=filters[k] }); if (filters.started_range?.length===2) [p.started_at_start,p.started_at_end]=filters.started_range; return p }
const buildParams = () => { const p = { page:page.value, count:count.value }; ['migration_type','status','chain_id','task_id','user_id','user_goods_id'].forEach(k => { if (filters[k] !== '' && filters[k] != null) p[k]=filters[k] }); if (embedded && serviceId.value) p[`${serviceDirection.value}_service_id`] = serviceId.value; if (filters.started_range?.length===2) [p.started_at_start,p.started_at_end]=filters.started_range; return p }
const loadList = async () => { loading.value=true; try { const res=await getVmMigrationRecordList(buildParams()); if(res?.data?.code!==200) throw new Error(extractApiError(res?.data,'加载迁移记录失败')); const d=res.data.data||{}; records.value=d.data||[]; total.value=d.all_count||0 } catch(e){ ElMessage.error(e.message||extractApiError(e?.response?.data,'加载迁移记录失败')) } finally { loading.value=false } }
const search = () => { page.value=1; loadList() }, sizeChange = () => { page.value=1; loadList() }
const resetFilters = () => { Object.assign(filters,{ migration_type:'',status:'',chain_id:'',task_id:'',user_id:null,user_goods_id:null,started_range:[] }); search() }
@@ -116,8 +123,8 @@ const prettySnapshot = value => { if(!value) return '暂无数据'; try { return
const openDetail = async row => { detailVisible.value=true; currentRecord.value=row; detailLoading.value=true; try { const res=await getVmMigrationRecordDetail({id:row.id}); if(res?.data?.code!==200) throw new Error(extractApiError(res?.data,'加载详情失败')); currentRecord.value=res.data.data?.record||res.data.data||row } catch(e){ ElMessage.error(e.message||extractApiError(e?.response?.data,'加载详情失败')) } finally { detailLoading.value=false } }
const chainVisible=ref(false), chainLoading=ref(false), chainData=reactive({chain_id:'',records:[]})
const openChain = async row => { chainVisible.value=true; chainLoading.value=true; Object.assign(chainData,{chain_id:row.chain_id||'',records:[]}); try { const params=row.chain_id?{chain_id:row.chain_id}:{id:row.id}; const res=await getVmMigrationRecordChain(params); if(res?.data?.code!==200) throw new Error(extractApiError(res?.data,'加载迁移链失败')); Object.assign(chainData,res.data.data||{}) } catch(e){ ElMessage.error(e.message||extractApiError(e?.response?.data,'加载迁移链失败')) } finally { chainLoading.value=false } }
const goVm=(serviceId,vmId)=>router.push({path:'/virtualization/vm-detail',query:{service_id:serviceId,vm_id:vmId}})
const goHost=(serviceId,hostId)=>router.push({path:'/virtualization/host-detail',query:{service_id:serviceId,host_id:hostId}})
const goVm=(targetServiceId,vmId)=>router.push({path:'/virtualization/vm-detail',query:{service_id:targetServiceId,vm_id:vmId}})
const goHost=(targetServiceId,hostId)=>router.push({path:'/virtualization/host-detail',query:{service_id:targetServiceId,host_id:hostId}})
const goUser=userId=>router.push({path:'/user/detail',query:{user_id:userId}})
const goUserGoods=id=>router.push(`/user-goods/detail/${id}`)
onMounted(async () => {