fix: 虚拟机详情监控模块
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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] } },
|
||||
]
|
||||
|
||||
// 加载历史指标数据
|
||||
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 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: 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' })
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user