feat(admin): KSM内存去重管理+监控图表增强+额度统计UI重构+流量管理合并 -- 缘由: 后端新增KSM状态/配置接口,监控数据改为绝对值,额度统计需可视化 -- 预期: HostDetail支持KSM查看/启停/调参,内存图表改为绝对值+磁盘IOPS图+流量趋势图,额度统计改为环形进度卡片,流量策略与统计合并为流量管理tab,订单代金券改为非必填,VmManage显示累计流量
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -631,6 +631,11 @@
|
||||
<div class="metric-summary-value">↓{{ formatNetLabel(latestMetrics.net_rx) }}</div>
|
||||
<div class="metric-summary-sub">↑{{ formatNetLabel(latestMetrics.net_tx) }}</div>
|
||||
</div>
|
||||
<div class="metric-summary-card">
|
||||
<div class="metric-summary-label">累计流量</div>
|
||||
<div class="metric-summary-value">{{ latestMetrics.traffic_used_mb != null ? latestMetrics.traffic_used_mb + ' MB' : '-' }}</div>
|
||||
<div class="metric-summary-sub"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="historicalMetricsData">
|
||||
@@ -643,7 +648,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="metrics-card">
|
||||
<template #header><span class="metrics-title"><el-icon><Refresh /></el-icon> 内存使用率</span></template>
|
||||
<template #header><span class="metrics-title"><el-icon><Refresh /></el-icon> 内存使用</span></template>
|
||||
<div ref="memChartRef" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
@@ -662,13 +667,44 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16" style="margin-top: 16px">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="metrics-card">
|
||||
<template #header><span class="metrics-title"><el-icon><Refresh /></el-icon> 磁盘 IOPS</span></template>
|
||||
<div ref="diskIopsChartRef" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="metrics-card">
|
||||
<template #header><span class="metrics-title"><el-icon><Refresh /></el-icon> 流量使用趋势</span></template>
|
||||
<div ref="trafficUsedChartRef" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-empty v-else-if="!historicalMetricsLoading" description="加载监控数据中..." :image-size="80" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 每小时流量 -->
|
||||
<el-tab-pane label="流量统计" name="trafficHourly">
|
||||
<!-- 流量管理(合并流量策略 + 流量统计) -->
|
||||
<el-tab-pane label="流量管理" name="trafficManage">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">流量策略</h3>
|
||||
<div style="display:flex;gap:8px">
|
||||
<el-button size="small" type="primary" @click="openVmTrafficPolicyDialog">修改流量策略</el-button>
|
||||
<el-button size="small" type="success" @click="openVmAddTrafficDialog('fixed')">增加固定流量</el-button>
|
||||
<el-button size="small" type="warning" @click="openVmAddTrafficDialog('temporary')">增加临时流量</el-button>
|
||||
<el-button size="small" :icon="Refresh" @click="loadVmTrafficPolicy" :loading="vmTrafficPolicyLoading">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-descriptions v-if="vmTrafficPolicy" :column="3" border size="small" style="margin-top:12px">
|
||||
<el-descriptions-item label="流量上限">{{ vmTrafficPolicy.traffic_max_mb != null ? (vmTrafficPolicy.traffic_max_mb === 0 ? '不限' : vmTrafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽下行限速">{{ vmTrafficPolicy.exhausted_rx_mbps != null ? (vmTrafficPolicy.exhausted_rx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽上行限速">{{ vmTrafficPolicy.exhausted_tx_mbps != null ? (vmTrafficPolicy.exhausted_tx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else-if="!vmTrafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
|
||||
</div>
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">每小时流量</h3>
|
||||
@@ -695,27 +731,6 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流量策略 -->
|
||||
<el-tab-pane label="流量策略" name="vmTrafficPolicy">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">流量策略</h3>
|
||||
<div style="display:flex;gap:8px">
|
||||
<el-button size="small" type="primary" @click="openVmTrafficPolicyDialog">修改流量策略</el-button>
|
||||
<el-button size="small" type="success" @click="openVmAddTrafficDialog('fixed')">增加固定流量</el-button>
|
||||
<el-button size="small" type="warning" @click="openVmAddTrafficDialog('temporary')">增加临时流量</el-button>
|
||||
<el-button size="small" :icon="Refresh" @click="loadVmTrafficPolicy" :loading="vmTrafficPolicyLoading">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-descriptions v-if="vmTrafficPolicy" :column="3" border size="small" style="margin-top:12px">
|
||||
<el-descriptions-item label="流量上限">{{ vmTrafficPolicy.traffic_max_mb != null ? (vmTrafficPolicy.traffic_max_mb === 0 ? '不限' : vmTrafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽下行限速">{{ vmTrafficPolicy.exhausted_rx_mbps != null ? (vmTrafficPolicy.exhausted_rx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽上行限速">{{ vmTrafficPolicy.exhausted_tx_mbps != null ? (vmTrafficPolicy.exhausted_tx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else-if="!vmTrafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
@@ -1883,10 +1898,14 @@ const cpuChartRef = ref(null)
|
||||
const memChartRef = ref(null)
|
||||
const netChartRef = ref(null)
|
||||
const diskChartRef = ref(null)
|
||||
const diskIopsChartRef = ref(null)
|
||||
const trafficUsedChartRef = ref(null)
|
||||
let cpuChart = null
|
||||
let memChart = null
|
||||
let netChart = null
|
||||
let diskChart = null
|
||||
let diskIopsChart = null
|
||||
let trafficUsedChart = null
|
||||
let isPageActive = false
|
||||
|
||||
const historicalMetricsData = ref(null)
|
||||
@@ -2014,9 +2033,28 @@ const renderHistoricalCharts = () => {
|
||||
})
|
||||
|
||||
const cpuData = metrics.map(m => m.cpu_usage ?? 0)
|
||||
const memData = metrics.map(m => m.mem_total ? ((m.mem_used / m.mem_total) * 100) : 0)
|
||||
const memData = metrics.map(m => m.mem_used ?? 0)
|
||||
const memTotal = Math.max(...metrics.map(m => m.mem_total ?? 0))
|
||||
const memAxisFmt = memTotal >= 1048576
|
||||
? (v) => (v / 1048576).toFixed(1) + ' GB'
|
||||
: memTotal >= 1024
|
||||
? (v) => (v / 1024).toFixed(0) + ' MB'
|
||||
: (v) => v + ' KB'
|
||||
|
||||
const diskReadData = metrics.map(m => m.disk_read ?? 0)
|
||||
const diskWriteData = metrics.map(m => m.disk_write ?? 0)
|
||||
|
||||
const diskReadRate = []
|
||||
const diskWriteRate = []
|
||||
for (let i = 0; i < metrics.length; i++) {
|
||||
if (i === 0) { diskReadRate.push(0); diskWriteRate.push(0); continue }
|
||||
const dt = (new Date(metrics[i].bucket) - new Date(metrics[i - 1].bucket)) / 1000
|
||||
if (dt > 0) {
|
||||
diskReadRate.push(+Math.max(0, ((metrics[i].disk_read ?? 0) - (metrics[i - 1].disk_read ?? 0)) / dt / 1024).toFixed(2))
|
||||
diskWriteRate.push(+Math.max(0, ((metrics[i].disk_write ?? 0) - (metrics[i - 1].disk_write ?? 0)) / dt / 1024).toFixed(2))
|
||||
} else { diskReadRate.push(0); diskWriteRate.push(0) }
|
||||
}
|
||||
|
||||
const netRxData = metrics.map(m => m.net_rx ?? 0)
|
||||
const netTxData = metrics.map(m => m.net_tx ?? 0)
|
||||
|
||||
@@ -2037,9 +2075,9 @@ const renderHistoricalCharts = () => {
|
||||
if (memChartRef.value) {
|
||||
if (!memChart) memChart = echarts.init(memChartRef.value)
|
||||
memChart.setOption({
|
||||
tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} 内存: ${p[0].value.toFixed(1)}%` },
|
||||
grid: baseGrid, xAxis: makeXAxis(),
|
||||
yAxis: { type: 'value', min: 0, max: 100, axisLabel: { fontSize: 10, formatter: v => v + '%' } },
|
||||
tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} 内存: ${formatMemKB(p[0].value)}` },
|
||||
grid: { ...baseGrid, left: 60 }, xAxis: makeXAxis(),
|
||||
yAxis: { type: 'value', min: 0, max: memTotal || undefined, axisLabel: { fontSize: 10, formatter: memAxisFmt } },
|
||||
series: [makeSeries('内存', memData, '#67c23a')]
|
||||
}, true)
|
||||
}
|
||||
@@ -2058,6 +2096,20 @@ const renderHistoricalCharts = () => {
|
||||
}, true)
|
||||
}
|
||||
|
||||
if (diskIopsChartRef.value) {
|
||||
if (!diskIopsChart) diskIopsChart = echarts.init(diskIopsChartRef.value)
|
||||
diskIopsChart.setOption({
|
||||
tooltip: { trigger: 'axis', formatter: (params) => {
|
||||
let s = params[0].axisValue
|
||||
params.forEach(p => { s += `<br/>${p.marker} ${p.seriesName}: ${formatBytesRaw(p.value)}/s` })
|
||||
return s
|
||||
}},
|
||||
grid: baseGrid, xAxis: makeXAxis(),
|
||||
yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: v => formatBytesRaw(v) + '/s' } },
|
||||
series: [makeSeries('读取', diskReadRate, '#409eff'), makeSeries('写入', diskWriteRate, '#e6a23c')]
|
||||
}, true)
|
||||
}
|
||||
|
||||
if (netChartRef.value) {
|
||||
if (!netChart) netChart = echarts.init(netChartRef.value)
|
||||
netChart.setOption({
|
||||
@@ -2071,6 +2123,22 @@ const renderHistoricalCharts = () => {
|
||||
series: [makeSeries('接收', netRxData, '#409eff'), makeSeries('发送', netTxData, '#e6a23c')]
|
||||
}, true)
|
||||
}
|
||||
|
||||
if (trafficUsedChartRef.value) {
|
||||
if (!trafficUsedChart) trafficUsedChart = echarts.init(trafficUsedChartRef.value)
|
||||
const trafficDelta = []
|
||||
for (let i = 0; i < metrics.length; i++) {
|
||||
if (i === 0) { trafficDelta.push(0); continue }
|
||||
const delta = Math.max(0, (metrics[i].traffic_used_mb ?? 0) - (metrics[i - 1].traffic_used_mb ?? 0))
|
||||
trafficDelta.push(+delta.toFixed(2))
|
||||
}
|
||||
trafficUsedChart.setOption({
|
||||
tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} 流量增量: ${p[0].value} MB` },
|
||||
grid: baseGrid, xAxis: makeXAxis(),
|
||||
yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: v => v + ' MB' } },
|
||||
series: [makeSeries('流量增量', trafficDelta, '#722ed1')]
|
||||
}, true)
|
||||
}
|
||||
}
|
||||
|
||||
const disposeCharts = () => {
|
||||
@@ -2078,6 +2146,9 @@ const disposeCharts = () => {
|
||||
memChart?.dispose(); memChart = null
|
||||
netChart?.dispose(); netChart = null
|
||||
diskChart?.dispose(); diskChart = null
|
||||
diskIopsChart?.dispose(); diskIopsChart = null
|
||||
trafficUsedChart?.dispose(); trafficUsedChart = null
|
||||
trafficHourlyChart?.dispose(); trafficHourlyChart = null
|
||||
}
|
||||
|
||||
const powerDialogVisible = ref(false)
|
||||
@@ -3821,7 +3892,7 @@ const triggerTabLoad = (tab) => {
|
||||
if (tab === 'backup') { loadBackups(); loadBackupQuota() }
|
||||
if (tab === 'userNetworking') loadVmNetworkingList()
|
||||
if (tab === 'security') loadSgLockInfo()
|
||||
if (tab === 'vmTrafficPolicy') loadVmTrafficPolicy()
|
||||
if (tab === 'trafficManage') { loadVmTrafficPolicy(); loadTrafficHourly() }
|
||||
}
|
||||
|
||||
// 请求安全组详情补充 lock 字段
|
||||
|
||||
Reference in New Issue
Block a user