fix: 虚拟机详情监控模块
Build and Deploy Vue3 / build (push) Successful in 1m30s
Build and Deploy Vue3 / deploy (push) Successful in 17m15s

This commit is contained in:
2026-04-15 18:38:08 +08:00
parent cae1f847e4
commit f53f63e679
3 changed files with 174 additions and 78 deletions
+59 -18
View File
@@ -336,12 +336,19 @@
<div class="section-block">
<div class="section-header">
<h3 class="section-title">监控指标</h3>
<div style="display: flex; align-items: center; gap: 8px">
<el-select v-model="monitorInterval" size="small" style="width: 120px" @change="loadMetricsHistory">
<el-option label="1分钟" value="1m" />
<el-option label="5分钟" value="5m" />
<el-option label="1小时" value="1h" />
</el-select>
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
<el-date-picker
v-model="monitorDateRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
size="small"
style="width: 360px"
:shortcuts="monitorShortcuts"
@change="loadMetricsHistory"
/>
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
<el-button size="small" :icon="Refresh" @click="loadMetricsHistory" :loading="metricsLoading">刷新</el-button>
</div>
</div>
@@ -1917,7 +1924,43 @@ let netChart = null
const metricsData = ref(null)
const metricsLoading = ref(false)
const monitorInterval = ref('1m')
const makeDefaultRange = () => {
const now = new Date()
return [new Date(now.getTime() - 10 * 60 * 1000), now]
}
const monitorDateRange = ref(makeDefaultRange())
const monitorShortcuts = [
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
]
function calcInterval(startTime, endTime) {
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
if (spanMin < 30) return '1m'
if (spanMin < 60) return '3m'
if (spanMin < 360) return '5m'
if (spanMin < 720) return '15m'
if (spanMin < 1440) return '30m'
if (spanMin < 4320) return '1h'
if (spanMin < 10080) return '2h'
if (spanMin < 43200) return '6h'
if (spanMin < 129600) return '12h'
return '1d'
}
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
const currentIntervalLabel = computed(() => {
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
return intervalLabelMap[iv] || iv
})
const latestMetrics = computed(() => {
const arr = metricsData.value
@@ -1960,20 +2003,16 @@ const vmMemPercent = (m) => {
const loadMetricsHistory = async () => {
if (!userGoodsId.value) return
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
metricsLoading.value = true
try {
const now = new Date()
let startTime = new Date(now)
const interval = monitorInterval.value
switch (interval) {
case '1m': startTime.setHours(now.getHours() - 1); break
case '5m': startTime.setHours(now.getHours() - 6); break
case '1h': startTime.setDate(now.getDate() - 1); break
}
const startTime = new Date(monitorDateRange.value[0])
const endTime = new Date(monitorDateRange.value[1])
const interval = calcInterval(startTime, endTime)
const params = {
user_goods_id: userGoodsId.value,
start: startTime.toISOString(),
end_time: now.toISOString(),
end_time: endTime.toISOString(),
interval
}
const res = await getUserVmMetricsHistory(params)
@@ -1996,7 +2035,9 @@ const renderMetricsCharts = () => {
const metrics = metricsData.value
if (!Array.isArray(metrics) || !metrics.length) return
const showDate = monitorInterval.value === '1h'
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 600000
const showDate = spanMs >= 86400000
const symbolType = metrics.length < 30 ? 'circle' : 'none'
const labelRotate = showDate ? 45 : 0
const times = metrics.map(m => {
@@ -2014,7 +2055,7 @@ const renderMetricsCharts = () => {
const baseGrid = { top: 10, right: 16, bottom: showDate ? 40 : 24, left: 50 }
const makeXAxis = () => ({ type: 'category', data: times, boundaryGap: false, axisLabel: { fontSize: 10, rotate: labelRotate } })
const makeSeries = (name, data, color) => ({ name, type: 'line', smooth: true, symbol: 'none', areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color }, itemStyle: { color }, data })
const makeSeries = (name, data, color) => ({ name, type: 'line', smooth: true, symbol: symbolType, areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color }, itemStyle: { color }, data })
if (cpuChartRef.value) {
if (!cpuChart) cpuChart = echarts.init(cpuChartRef.value)
+60 -40
View File
@@ -137,10 +137,19 @@
<div class="section-block">
<div class="section-header">
<h3 class="section-title">监控指标</h3>
<div style="display: flex; align-items: center; gap: 8px;">
<el-select v-model="historyTimeRange" size="small" style="width: 120px" @change="loadHistoricalMetrics">
<el-option v-for="option in historyTimeOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
<el-date-picker
v-model="monitorDateRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
size="small"
style="width: 360px"
:shortcuts="monitorShortcuts"
@change="loadHistoricalMetrics"
/>
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
<el-button size="small" :icon="Refresh" @click="loadHistoricalMetrics" :loading="historicalMetricsLoading">刷新</el-button>
</div>
</div>
@@ -710,46 +719,57 @@ const latestMetrics = computed(() => {
return arr[arr.length - 1]
})
// 历史指标时间范围
const historyTimeRange = ref('1m') // 1m, 5m, 1h, 1d
const historyTimeOptions = [
{ label: '最近1分钟', value: '1m' },
{ label: '最近5分钟', value: '5m' },
{ label: '最近1小时', value: '1h' },
{ label: '最近1天', value: '1d' },
const makeDefaultRange = () => {
const now = new Date()
return [new Date(now.getTime() - 10 * 60 * 1000), now]
}
const monitorDateRange = ref(makeDefaultRange())
const monitorShortcuts = [
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
]
// 加载历史指标数据
const loadHistoricalMetrics = async () => {
if (!serviceId.value || !hostId.value) return
historicalMetricsLoading.value = true
try {
// 计算时间范围
const now = new Date()
let startTime = new Date()
switch (historyTimeRange.value) {
case '1m':
startTime.setMinutes(now.getMinutes() - 1)
break
case '5m':
startTime.setMinutes(now.getMinutes() - 5)
break
case '1h':
startTime.setHours(now.getHours() - 1)
break
case '1d':
startTime.setDate(now.getDate() - 1)
break
function calcInterval(startTime, endTime) {
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
if (spanMin < 30) return '1m'
if (spanMin < 60) return '3m'
if (spanMin < 360) return '5m'
if (spanMin < 720) return '15m'
if (spanMin < 1440) return '30m'
if (spanMin < 4320) return '1h'
if (spanMin < 10080) return '2h'
if (spanMin < 43200) return '6h'
if (spanMin < 129600) return '12h'
return '1d'
}
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
const currentIntervalLabel = computed(() => {
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
return intervalLabelMap[iv] || iv
})
const loadHistoricalMetrics = async () => {
if (!serviceId.value || !hostId.value) return
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
historicalMetricsLoading.value = true
try {
const startTime = new Date(monitorDateRange.value[0])
const endTime = new Date(monitorDateRange.value[1])
const interval = calcInterval(startTime, endTime)
const params = {
service_id: serviceId.value,
host_id: hostId.value,
start: startTime.toISOString(),
end_time: now.toISOString(),
interval: { '1m': '1m', '5m': '5m', '1h': '1h', '1d': '1d' }[historyTimeRange.value] || '5m'
end_time: endTime.toISOString(),
interval
}
const res = await getMetricsHistory(params)
@@ -782,14 +802,14 @@ const renderHistoricalCharts = () => {
const metrics = historicalMetricsData.value
if (!Array.isArray(metrics) || !metrics.length) return
const range = historyTimeRange.value
const showDate = range === '7d' || range === '24h'
const symbolType = range === '7d' ? 'circle' : 'none'
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 0
const showDate = spanMs >= 12 * 3600 * 1000
const symbolType = spanMs >= 7 * 86400 * 1000 ? 'circle' : 'none'
const labelRotate = showDate ? 45 : 0
const times = metrics.map(m => {
const date = new Date(m.bucket)
if (range === '7d') return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
if (showDate) return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
return date.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' })
})
+57 -22
View File
@@ -538,10 +538,19 @@
<div class="section-block">
<div class="section-header">
<h3 class="section-title">监控指标</h3>
<div style="display: flex; align-items: center; gap: 8px">
<el-select v-model="historyTimeRange" size="small" style="width: 120px" @change="loadHistoricalMetrics">
<el-option v-for="option in historyTimeOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
<el-date-picker
v-model="monitorDateRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
size="small"
style="width: 360px"
:shortcuts="monitorShortcuts"
@change="loadHistoricalMetrics"
/>
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
<el-button size="small" :icon="Refresh" @click="loadHistoricalMetrics" :loading="historicalMetricsLoading">刷新</el-button>
</div>
</div>
@@ -1657,14 +1666,44 @@ let isPageActive = false
const historicalMetricsData = ref(null)
const historicalMetricsLoading = ref(false)
const historyTimeRange = ref('1h')
const historyTimeOptions = [
{ label: '最近1分钟', value: '1m' },
{ label: '最近5分钟', value: '5m' },
{ label: '最近1小时', value: '1h' },
{ label: '最近1天', value: '1d' }
const makeDefaultRange = () => {
const now = new Date()
return [new Date(now.getTime() - 10 * 60 * 1000), now]
}
const monitorDateRange = ref(makeDefaultRange())
const monitorShortcuts = [
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
]
function calcInterval(startTime, endTime) {
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
if (spanMin < 30) return '1m'
if (spanMin < 60) return '3m'
if (spanMin < 360) return '5m'
if (spanMin < 720) return '15m'
if (spanMin < 1440) return '30m'
if (spanMin < 4320) return '1h'
if (spanMin < 10080) return '2h'
if (spanMin < 43200) return '6h'
if (spanMin < 129600) return '12h'
return '1d'
}
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
const currentIntervalLabel = computed(() => {
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
return intervalLabelMap[iv] || iv
})
const latestMetrics = computed(() => {
const arr = historicalMetricsData.value
if (!Array.isArray(arr) || !arr.length) return null
@@ -1704,23 +1743,19 @@ const vmMemPercent = (m) => {
const loadHistoricalMetrics = async () => {
if (!serviceId.value || !detail.value?.name) return
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
historicalMetricsLoading.value = true
try {
const now = new Date()
let startTime = new Date()
switch (historyTimeRange.value) {
case '1m': startTime.setMinutes(now.getMinutes() - 1); break
case '5m': startTime.setMinutes(now.getMinutes() - 5); break
case '1h': startTime.setHours(now.getHours() - 1); break
case '1d': startTime.setDate(now.getDate() - 1); break
}
const startTime = new Date(monitorDateRange.value[0])
const endTime = new Date(monitorDateRange.value[1])
const interval = calcInterval(startTime, endTime)
const params = {
service_id: serviceId.value,
host_id: vmHostId.value,
vm_name: detail.value.name,
start: startTime.toISOString(),
end_time: now.toISOString(),
interval: { '1m': '1m', '5m': '5m', '1h': '1h', '1d': '1d' }[historyTimeRange.value] || '5m'
end_time: endTime.toISOString(),
interval
}
const res = await getMetricsHistory(params)
const body = res?.data
@@ -1742,8 +1777,8 @@ const renderHistoricalCharts = () => {
const metrics = historicalMetricsData.value
if (!Array.isArray(metrics) || !metrics.length) return
const range = historyTimeRange.value
const showDate = range === '1d'
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 0
const showDate = spanMs >= 12 * 3600 * 1000
const symbolType = showDate ? 'circle' : 'none'
const labelRotate = showDate ? 45 : 0