feat(admin+user): 虚拟机断网/恢复网络+每小时流量图表+宿主机额度统计 -- 缘由: 后端新增disconnect/connect_network,traffic_hourly,quota_stats接口,VM新增network_disabled字段 -- 预期: VmDetail/UserVmDetail/用户详情支持断网恢复操作并显示断网状态,VmDetail新增流量统计tab,HostDetail新增额度统计tab
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -153,6 +153,42 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="额度统计" name="quotaStats">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">资源额度统计</h3>
|
||||
<el-button size="small" :icon="Refresh" @click="loadQuotaStats" :loading="quotaStatsLoading">刷新</el-button>
|
||||
</div>
|
||||
<template v-if="quotaStats">
|
||||
<el-descriptions :column="3" border size="small" style="margin-top:12px">
|
||||
<el-descriptions-item label="虚拟机数量">{{ quotaStats.vm_count ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规划 CPU">{{ quotaStats.planned_cpu ?? '-' }} 核</el-descriptions-item>
|
||||
<el-descriptions-item label="已分配 CPU">{{ quotaStats.allocated_cpu ?? '-' }} 核</el-descriptions-item>
|
||||
<el-descriptions-item label="规划内存">{{ formatQuotaMem(quotaStats.planned_memory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="已分配内存">{{ formatQuotaMem(quotaStats.allocated_memory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实时内存">{{ formatQuotaBytes(quotaStats.actual_memory_used) }} / {{ formatQuotaBytes(quotaStats.actual_memory_total) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规划磁盘">{{ quotaStats.planned_disk ?? '-' }} GB</el-descriptions-item>
|
||||
<el-descriptions-item label="已分配磁盘">{{ quotaStats.allocated_disk ?? '-' }} GB</el-descriptions-item>
|
||||
<el-descriptions-item label="实时 CPU">{{ quotaStats.actual_cpu_percent != null ? quotaStats.actual_cpu_percent.toFixed(1) + '%' : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="规划下行带宽">{{ quotaStats.planned_rx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
|
||||
<el-descriptions-item label="已分配下行带宽">{{ quotaStats.allocated_rx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
|
||||
<el-descriptions-item label="规划上行带宽">{{ quotaStats.planned_tx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
|
||||
<el-descriptions-item label="已分配上行带宽">{{ quotaStats.allocated_tx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template v-if="quotaStatsDisk.length">
|
||||
<h4 style="margin:16px 0 8px;font-size:13px;color:#606266">磁盘使用</h4>
|
||||
<el-table :data="quotaStatsDisk" size="small" stripe>
|
||||
<el-table-column prop="path" label="路径" min-width="160" />
|
||||
<el-table-column label="总量" width="100"><template #default="{row}">{{ formatQuotaBytes(row.total) }}</template></el-table-column>
|
||||
<el-table-column label="已用" width="100"><template #default="{row}">{{ formatQuotaBytes(row.used) }}</template></el-table-column>
|
||||
<el-table-column label="使用率" width="100"><template #default="{row}">{{ row.total ? ((row.used / row.total) * 100).toFixed(1) + '%' : '-' }}</template></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</template>
|
||||
<el-empty v-else-if="!quotaStatsLoading" description="暂无额度统计数据" :image-size="60" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="监控" name="monitor">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
@@ -613,7 +649,7 @@ import {
|
||||
getRemoteHostDetail, updateRemoteHost, deleteRemoteHost,
|
||||
getUserNetworkingList, getUserNetworkingDetail, createUserNetworking, deleteUserNetworking,
|
||||
assignUserNetworking, removeUserNetworkingNetwork,
|
||||
createHostToken, getMetricsHistory
|
||||
createHostToken, getMetricsHistory, getHostQuotaStats
|
||||
} from '@/api/admin/kvmService'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
import { baseUrl } from '@/config/env'
|
||||
@@ -662,6 +698,7 @@ watch(activeTab, (tab) => {
|
||||
}
|
||||
}
|
||||
if (tab === 'networking') loadNetworkingList()
|
||||
if (tab === 'quotaStats' && !quotaStats.value) loadQuotaStats()
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -822,6 +859,39 @@ const loadDetail = async () => {
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '加载失败')) } finally { loading.value = false }
|
||||
}
|
||||
|
||||
// ---- 额度统计 ----
|
||||
const quotaStats = ref(null)
|
||||
const quotaStatsLoading = ref(false)
|
||||
const quotaStatsDisk = computed(() => {
|
||||
if (!quotaStats.value?.actual_disk_json) return []
|
||||
try { return JSON.parse(quotaStats.value.actual_disk_json) } catch { return [] }
|
||||
})
|
||||
|
||||
const loadQuotaStats = async () => {
|
||||
if (!serviceId.value || !hostId.value) return
|
||||
quotaStatsLoading.value = true
|
||||
try {
|
||||
const res = await getHostQuotaStats({ service_id: serviceId.value, host_id: hostId.value })
|
||||
if (res?.data?.code === 200) quotaStats.value = res.data.data
|
||||
else quotaStats.value = null
|
||||
} catch { quotaStats.value = null } finally { quotaStatsLoading.value = false }
|
||||
}
|
||||
|
||||
const formatQuotaMem = (mb) => {
|
||||
if (!mb && mb !== 0) return '-'
|
||||
if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB'
|
||||
return mb + ' MB'
|
||||
}
|
||||
|
||||
const formatQuotaBytes = (bytes) => {
|
||||
if (!bytes && bytes !== 0) return '-'
|
||||
const n = Number(bytes)
|
||||
if (n >= 1073741824) return (n / 1073741824).toFixed(2) + ' GB'
|
||||
if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB'
|
||||
if (n >= 1024) return (n / 1024).toFixed(0) + ' KB'
|
||||
return n + ' B'
|
||||
}
|
||||
|
||||
const cpuChartRef = ref(null)
|
||||
const memChartRef = ref(null)
|
||||
const netChartRef = ref(null)
|
||||
|
||||
Reference in New Issue
Block a user