feat(monitor): 监控时间选择器统一为相对时间+自定义范围双模式
- VmMonitor/VmDetail/UserVmDetail/HostDetail 四个页面统一改造 - 支持「最近」模式(动态计算时间范围)和「自定义」模式(固定日期范围) - 每小时流量图表同步应用双模式选择器 - 移除旧的 monitorShortcuts 快捷方式 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -402,15 +402,28 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3 class="section-title">监控指标</h3>
|
<h3 class="section-title">监控指标</h3>
|
||||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<el-radio-group v-model="monitorTimeMode" size="small" @change="loadMetricsHistory">
|
||||||
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-select v-if="monitorTimeMode === 'relative'" v-model="monitorRelativeMinutes" size="small" style="width: 120px" @change="loadMetricsHistory">
|
||||||
|
<el-option label="10分钟" :value="10" />
|
||||||
|
<el-option label="30分钟" :value="30" />
|
||||||
|
<el-option label="1小时" :value="60" />
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="1天" :value="1440" />
|
||||||
|
<el-option label="7天" :value="10080" />
|
||||||
|
</el-select>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
|
v-else
|
||||||
v-model="monitorDateRange"
|
v-model="monitorDateRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 360px"
|
style="width: 340px"
|
||||||
:shortcuts="monitorShortcuts"
|
|
||||||
@change="loadMetricsHistory"
|
@change="loadMetricsHistory"
|
||||||
/>
|
/>
|
||||||
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
||||||
@@ -516,16 +529,27 @@
|
|||||||
<div class="section-block" style="margin-top:16px">
|
<div class="section-block" style="margin-top:16px">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3 class="section-title">每小时流量</h3>
|
<h3 class="section-title">每小时流量</h3>
|
||||||
<div style="display: flex; align-items: center; gap: 8px">
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<el-radio-group v-model="trafficTimeMode" size="small" @change="loadTrafficHourly">
|
||||||
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-select v-if="trafficTimeMode === 'relative'" v-model="trafficRelativeMinutes" size="small" style="width: 120px" @change="loadTrafficHourly">
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="1天" :value="1440" />
|
||||||
|
<el-option label="3天" :value="4320" />
|
||||||
|
<el-option label="7天" :value="10080" />
|
||||||
|
</el-select>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
|
v-else
|
||||||
v-model="trafficHourlyRange"
|
v-model="trafficHourlyRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 360px"
|
style="width: 340px"
|
||||||
:shortcuts="monitorShortcuts"
|
|
||||||
@change="loadTrafficHourly"
|
@change="loadTrafficHourly"
|
||||||
/>
|
/>
|
||||||
<el-button size="small" :icon="Refresh" @click="loadTrafficHourly" :loading="trafficHourlyLoading">刷新</el-button>
|
<el-button size="small" :icon="Refresh" @click="loadTrafficHourly" :loading="trafficHourlyLoading">刷新</el-button>
|
||||||
@@ -2259,24 +2283,35 @@ const submitAddTraffic = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 每小时流量统计 ----
|
// ---- 每小时流量统计 ----
|
||||||
|
const trafficTimeMode = ref('relative')
|
||||||
|
const trafficRelativeMinutes = ref(1440)
|
||||||
const trafficHourlyRange = ref(null)
|
const trafficHourlyRange = ref(null)
|
||||||
const trafficHourlyData = ref([])
|
const trafficHourlyData = ref([])
|
||||||
const trafficHourlyLoading = ref(false)
|
const trafficHourlyLoading = ref(false)
|
||||||
const trafficHourlyChartRef = ref(null)
|
const trafficHourlyChartRef = ref(null)
|
||||||
let trafficHourlyChart = null
|
let trafficHourlyChart = null
|
||||||
|
|
||||||
|
const getTrafficTimeRange = () => {
|
||||||
|
if (trafficTimeMode.value === 'relative') {
|
||||||
|
const endTime = new Date()
|
||||||
|
const startTime = new Date(endTime - trafficRelativeMinutes.value * 60 * 1000)
|
||||||
|
return { startTime, endTime }
|
||||||
|
} else {
|
||||||
|
if (!trafficHourlyRange.value || trafficHourlyRange.value.length < 2) return null
|
||||||
|
return { startTime: new Date(trafficHourlyRange.value[0]), endTime: new Date(trafficHourlyRange.value[1]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadTrafficHourly = async () => {
|
const loadTrafficHourly = async () => {
|
||||||
if (!userGoodsId.value) return
|
if (!userGoodsId.value) return
|
||||||
if (!trafficHourlyRange.value) {
|
const range = getTrafficTimeRange()
|
||||||
const now = new Date()
|
if (!range) return
|
||||||
trafficHourlyRange.value = [new Date(now.getTime() - 24 * 3600 * 1000), now]
|
|
||||||
}
|
|
||||||
trafficHourlyLoading.value = true
|
trafficHourlyLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getUserVmTrafficHourly({
|
const res = await getUserVmTrafficHourly({
|
||||||
user_goods_id: userGoodsId.value,
|
user_goods_id: userGoodsId.value,
|
||||||
start: new Date(trafficHourlyRange.value[0]).toISOString(),
|
start: range.startTime.toISOString(),
|
||||||
end_time: new Date(trafficHourlyRange.value[1]).toISOString()
|
end_time: range.endTime.toISOString()
|
||||||
})
|
})
|
||||||
const raw = res?.data?.data?.data
|
const raw = res?.data?.data?.data
|
||||||
trafficHourlyData.value = typeof raw === 'string' ? JSON.parse(raw) : (Array.isArray(raw) ? raw : [])
|
trafficHourlyData.value = typeof raw === 'string' ? JSON.parse(raw) : (Array.isArray(raw) ? raw : [])
|
||||||
@@ -2372,21 +2407,20 @@ let trafficUsedChart = null
|
|||||||
const metricsData = ref(null)
|
const metricsData = ref(null)
|
||||||
const metricsLoading = ref(false)
|
const metricsLoading = ref(false)
|
||||||
|
|
||||||
const makeDefaultRange = () => {
|
const monitorTimeMode = ref('relative')
|
||||||
const now = new Date()
|
const monitorRelativeMinutes = ref(10)
|
||||||
return [new Date(now.getTime() - 10 * 60 * 1000), now]
|
const monitorDateRange = ref(null)
|
||||||
}
|
|
||||||
const monitorDateRange = ref(makeDefaultRange())
|
|
||||||
|
|
||||||
const monitorShortcuts = [
|
const getMonitorTimeRange = () => {
|
||||||
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
|
if (monitorTimeMode.value === 'relative') {
|
||||||
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
|
const endTime = new Date()
|
||||||
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
|
const startTime = new Date(endTime - monitorRelativeMinutes.value * 60 * 1000)
|
||||||
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
|
return { startTime, endTime }
|
||||||
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
|
} else {
|
||||||
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
|
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return null
|
||||||
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
|
return { startTime: new Date(monitorDateRange.value[0]), endTime: new Date(monitorDateRange.value[1]) }
|
||||||
]
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calcInterval(startTime, endTime) {
|
function calcInterval(startTime, endTime) {
|
||||||
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
||||||
@@ -2404,8 +2438,9 @@ function calcInterval(startTime, endTime) {
|
|||||||
|
|
||||||
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
|
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(() => {
|
const currentIntervalLabel = computed(() => {
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
|
const range = getMonitorTimeRange()
|
||||||
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
|
if (!range) return '-'
|
||||||
|
const iv = calcInterval(range.startTime, range.endTime)
|
||||||
return intervalLabelMap[iv] || iv
|
return intervalLabelMap[iv] || iv
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2450,11 +2485,11 @@ const vmMemPercent = (m) => {
|
|||||||
|
|
||||||
const loadMetricsHistory = async () => {
|
const loadMetricsHistory = async () => {
|
||||||
if (!userGoodsId.value) return
|
if (!userGoodsId.value) return
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
|
const range = getMonitorTimeRange()
|
||||||
|
if (!range) return
|
||||||
metricsLoading.value = true
|
metricsLoading.value = true
|
||||||
try {
|
try {
|
||||||
const startTime = new Date(monitorDateRange.value[0])
|
const { startTime, endTime } = range
|
||||||
const endTime = new Date(monitorDateRange.value[1])
|
|
||||||
const interval = calcInterval(startTime, endTime)
|
const interval = calcInterval(startTime, endTime)
|
||||||
const params = {
|
const params = {
|
||||||
user_goods_id: userGoodsId.value,
|
user_goods_id: userGoodsId.value,
|
||||||
@@ -2482,7 +2517,8 @@ const renderMetricsCharts = () => {
|
|||||||
const metrics = metricsData.value
|
const metrics = metricsData.value
|
||||||
if (!Array.isArray(metrics) || !metrics.length) return
|
if (!Array.isArray(metrics) || !metrics.length) return
|
||||||
|
|
||||||
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 600000
|
const range = getMonitorTimeRange()
|
||||||
|
const spanMs = range ? (range.endTime.getTime() - range.startTime.getTime()) : 600000
|
||||||
const showDate = spanMs >= 86400000
|
const showDate = spanMs >= 86400000
|
||||||
const symbolType = metrics.length < 30 ? 'circle' : 'none'
|
const symbolType = metrics.length < 30 ? 'circle' : 'none'
|
||||||
const labelRotate = showDate ? 45 : 0
|
const labelRotate = showDate ? 45 : 0
|
||||||
|
|||||||
@@ -259,15 +259,28 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3 class="section-title">监控指标</h3>
|
<h3 class="section-title">监控指标</h3>
|
||||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<el-radio-group v-model="monitorTimeMode" size="small" @change="loadHistoricalMetrics">
|
||||||
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-select v-if="monitorTimeMode === 'relative'" v-model="monitorRelativeMinutes" size="small" style="width: 120px" @change="loadHistoricalMetrics">
|
||||||
|
<el-option label="10分钟" :value="10" />
|
||||||
|
<el-option label="30分钟" :value="30" />
|
||||||
|
<el-option label="1小时" :value="60" />
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="1天" :value="1440" />
|
||||||
|
<el-option label="7天" :value="10080" />
|
||||||
|
</el-select>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
|
v-else
|
||||||
v-model="monitorDateRange"
|
v-model="monitorDateRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 360px"
|
style="width: 340px"
|
||||||
:shortcuts="monitorShortcuts"
|
|
||||||
@change="loadHistoricalMetrics"
|
@change="loadHistoricalMetrics"
|
||||||
/>
|
/>
|
||||||
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
||||||
@@ -1109,21 +1122,20 @@ const latestMetrics = computed(() => {
|
|||||||
return arr[arr.length - 1]
|
return arr[arr.length - 1]
|
||||||
})
|
})
|
||||||
|
|
||||||
const makeDefaultRange = () => {
|
const monitorTimeMode = ref('relative')
|
||||||
const now = new Date()
|
const monitorRelativeMinutes = ref(10)
|
||||||
return [new Date(now.getTime() - 10 * 60 * 1000), now]
|
const monitorDateRange = ref(null)
|
||||||
}
|
|
||||||
const monitorDateRange = ref(makeDefaultRange())
|
|
||||||
|
|
||||||
const monitorShortcuts = [
|
const getMonitorTimeRange = () => {
|
||||||
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
|
if (monitorTimeMode.value === 'relative') {
|
||||||
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
|
const endTime = new Date()
|
||||||
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
|
const startTime = new Date(endTime - monitorRelativeMinutes.value * 60 * 1000)
|
||||||
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
|
return { startTime, endTime }
|
||||||
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
|
} else {
|
||||||
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
|
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return null
|
||||||
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
|
return { startTime: new Date(monitorDateRange.value[0]), endTime: new Date(monitorDateRange.value[1]) }
|
||||||
]
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calcInterval(startTime, endTime) {
|
function calcInterval(startTime, endTime) {
|
||||||
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
||||||
@@ -1141,18 +1153,19 @@ function calcInterval(startTime, endTime) {
|
|||||||
|
|
||||||
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
|
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(() => {
|
const currentIntervalLabel = computed(() => {
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
|
const range = getMonitorTimeRange()
|
||||||
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
|
if (!range) return '-'
|
||||||
|
const iv = calcInterval(range.startTime, range.endTime)
|
||||||
return intervalLabelMap[iv] || iv
|
return intervalLabelMap[iv] || iv
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadHistoricalMetrics = async () => {
|
const loadHistoricalMetrics = async () => {
|
||||||
if (!serviceId.value || !hostId.value) return
|
if (!serviceId.value || !hostId.value) return
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
|
const range = getMonitorTimeRange()
|
||||||
|
if (!range) return
|
||||||
historicalMetricsLoading.value = true
|
historicalMetricsLoading.value = true
|
||||||
try {
|
try {
|
||||||
const startTime = new Date(monitorDateRange.value[0])
|
const { startTime, endTime } = range
|
||||||
const endTime = new Date(monitorDateRange.value[1])
|
|
||||||
const interval = calcInterval(startTime, endTime)
|
const interval = calcInterval(startTime, endTime)
|
||||||
const params = {
|
const params = {
|
||||||
service_id: serviceId.value,
|
service_id: serviceId.value,
|
||||||
@@ -1192,7 +1205,8 @@ const renderHistoricalCharts = () => {
|
|||||||
const metrics = historicalMetricsData.value
|
const metrics = historicalMetricsData.value
|
||||||
if (!Array.isArray(metrics) || !metrics.length) return
|
if (!Array.isArray(metrics) || !metrics.length) return
|
||||||
|
|
||||||
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 0
|
const range = getMonitorTimeRange()
|
||||||
|
const spanMs = range ? (range.endTime.getTime() - range.startTime.getTime()) : 0
|
||||||
const showDate = spanMs >= 12 * 3600 * 1000
|
const showDate = spanMs >= 12 * 3600 * 1000
|
||||||
const symbolType = spanMs >= 7 * 86400 * 1000 ? 'circle' : 'none'
|
const symbolType = spanMs >= 7 * 86400 * 1000 ? 'circle' : 'none'
|
||||||
const labelRotate = showDate ? 45 : 0
|
const labelRotate = showDate ? 45 : 0
|
||||||
|
|||||||
@@ -594,15 +594,28 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3 class="section-title">监控指标</h3>
|
<h3 class="section-title">监控指标</h3>
|
||||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<el-radio-group v-model="monitorTimeMode" size="small" @change="loadHistoricalMetrics">
|
||||||
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-select v-if="monitorTimeMode === 'relative'" v-model="monitorRelativeMinutes" size="small" style="width: 120px" @change="loadHistoricalMetrics">
|
||||||
|
<el-option label="10分钟" :value="10" />
|
||||||
|
<el-option label="30分钟" :value="30" />
|
||||||
|
<el-option label="1小时" :value="60" />
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="1天" :value="1440" />
|
||||||
|
<el-option label="7天" :value="10080" />
|
||||||
|
</el-select>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
|
v-else
|
||||||
v-model="monitorDateRange"
|
v-model="monitorDateRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 360px"
|
style="width: 340px"
|
||||||
:shortcuts="monitorShortcuts"
|
|
||||||
@change="loadHistoricalMetrics"
|
@change="loadHistoricalMetrics"
|
||||||
/>
|
/>
|
||||||
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
<span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
|
||||||
@@ -708,16 +721,27 @@
|
|||||||
<div class="section-block">
|
<div class="section-block">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h3 class="section-title">每小时流量</h3>
|
<h3 class="section-title">每小时流量</h3>
|
||||||
<div style="display: flex; align-items: center; gap: 8px">
|
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<el-radio-group v-model="trafficTimeMode" size="small" @change="loadTrafficHourly">
|
||||||
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<el-select v-if="trafficTimeMode === 'relative'" v-model="trafficRelativeMinutes" size="small" style="width: 120px" @change="loadTrafficHourly">
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="1天" :value="1440" />
|
||||||
|
<el-option label="3天" :value="4320" />
|
||||||
|
<el-option label="7天" :value="10080" />
|
||||||
|
</el-select>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
|
v-else
|
||||||
v-model="trafficHourlyRange"
|
v-model="trafficHourlyRange"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 360px"
|
style="width: 340px"
|
||||||
:shortcuts="monitorShortcuts"
|
|
||||||
@change="loadTrafficHourly"
|
@change="loadTrafficHourly"
|
||||||
/>
|
/>
|
||||||
<el-button size="small" :icon="Refresh" @click="loadTrafficHourly" :loading="trafficHourlyLoading">刷新</el-button>
|
<el-button size="small" :icon="Refresh" @click="loadTrafficHourly" :loading="trafficHourlyLoading">刷新</el-button>
|
||||||
@@ -1911,21 +1935,20 @@ let isPageActive = false
|
|||||||
const historicalMetricsData = ref(null)
|
const historicalMetricsData = ref(null)
|
||||||
const historicalMetricsLoading = ref(false)
|
const historicalMetricsLoading = ref(false)
|
||||||
|
|
||||||
const makeDefaultRange = () => {
|
const monitorTimeMode = ref('relative')
|
||||||
const now = new Date()
|
const monitorRelativeMinutes = ref(10)
|
||||||
return [new Date(now.getTime() - 10 * 60 * 1000), now]
|
const monitorDateRange = ref(null)
|
||||||
}
|
|
||||||
const monitorDateRange = ref(makeDefaultRange())
|
|
||||||
|
|
||||||
const monitorShortcuts = [
|
const getMonitorTimeRange = () => {
|
||||||
{ text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
|
if (monitorTimeMode.value === 'relative') {
|
||||||
{ text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
|
const endTime = new Date()
|
||||||
{ text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
|
const startTime = new Date(endTime - monitorRelativeMinutes.value * 60 * 1000)
|
||||||
{ text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
|
return { startTime, endTime }
|
||||||
{ text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
|
} else {
|
||||||
{ text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
|
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return null
|
||||||
{ text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
|
return { startTime: new Date(monitorDateRange.value[0]), endTime: new Date(monitorDateRange.value[1]) }
|
||||||
]
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calcInterval(startTime, endTime) {
|
function calcInterval(startTime, endTime) {
|
||||||
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
|
||||||
@@ -1943,8 +1966,9 @@ function calcInterval(startTime, endTime) {
|
|||||||
|
|
||||||
const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
|
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(() => {
|
const currentIntervalLabel = computed(() => {
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
|
const range = getMonitorTimeRange()
|
||||||
const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
|
if (!range) return '-'
|
||||||
|
const iv = calcInterval(range.startTime, range.endTime)
|
||||||
return intervalLabelMap[iv] || iv
|
return intervalLabelMap[iv] || iv
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1987,11 +2011,11 @@ const vmMemPercent = (m) => {
|
|||||||
|
|
||||||
const loadHistoricalMetrics = async () => {
|
const loadHistoricalMetrics = async () => {
|
||||||
if (!serviceId.value || !detail.value?.name) return
|
if (!serviceId.value || !detail.value?.name) return
|
||||||
if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
|
const range = getMonitorTimeRange()
|
||||||
|
if (!range) return
|
||||||
historicalMetricsLoading.value = true
|
historicalMetricsLoading.value = true
|
||||||
try {
|
try {
|
||||||
const startTime = new Date(monitorDateRange.value[0])
|
const { startTime, endTime } = range
|
||||||
const endTime = new Date(monitorDateRange.value[1])
|
|
||||||
const interval = calcInterval(startTime, endTime)
|
const interval = calcInterval(startTime, endTime)
|
||||||
const params = {
|
const params = {
|
||||||
service_id: serviceId.value,
|
service_id: serviceId.value,
|
||||||
@@ -2021,7 +2045,8 @@ const renderHistoricalCharts = () => {
|
|||||||
const metrics = historicalMetricsData.value
|
const metrics = historicalMetricsData.value
|
||||||
if (!Array.isArray(metrics) || !metrics.length) return
|
if (!Array.isArray(metrics) || !metrics.length) return
|
||||||
|
|
||||||
const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 0
|
const range = getMonitorTimeRange()
|
||||||
|
const spanMs = range ? (range.endTime.getTime() - range.startTime.getTime()) : 0
|
||||||
const showDate = spanMs >= 12 * 3600 * 1000
|
const showDate = spanMs >= 12 * 3600 * 1000
|
||||||
const symbolType = showDate ? 'circle' : 'none'
|
const symbolType = showDate ? 'circle' : 'none'
|
||||||
const labelRotate = showDate ? 45 : 0
|
const labelRotate = showDate ? 45 : 0
|
||||||
@@ -3003,26 +3028,37 @@ const handleConnectNetwork = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 每小时流量统计 ----
|
// ---- 每小时流量统计 ----
|
||||||
|
const trafficTimeMode = ref('relative')
|
||||||
|
const trafficRelativeMinutes = ref(1440)
|
||||||
const trafficHourlyRange = ref(null)
|
const trafficHourlyRange = ref(null)
|
||||||
const trafficHourlyData = ref([])
|
const trafficHourlyData = ref([])
|
||||||
const trafficHourlyLoading = ref(false)
|
const trafficHourlyLoading = ref(false)
|
||||||
const trafficHourlyChartRef = ref(null)
|
const trafficHourlyChartRef = ref(null)
|
||||||
let trafficHourlyChart = null
|
let trafficHourlyChart = null
|
||||||
|
|
||||||
|
const getTrafficTimeRange = () => {
|
||||||
|
if (trafficTimeMode.value === 'relative') {
|
||||||
|
const endTime = new Date()
|
||||||
|
const startTime = new Date(endTime - trafficRelativeMinutes.value * 60 * 1000)
|
||||||
|
return { startTime, endTime }
|
||||||
|
} else {
|
||||||
|
if (!trafficHourlyRange.value || trafficHourlyRange.value.length < 2) return null
|
||||||
|
return { startTime: new Date(trafficHourlyRange.value[0]), endTime: new Date(trafficHourlyRange.value[1]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadTrafficHourly = async () => {
|
const loadTrafficHourly = async () => {
|
||||||
if (!serviceId.value || !detail.value?.host_id || !detail.value?.name) return
|
if (!serviceId.value || !detail.value?.host_id || !detail.value?.name) return
|
||||||
if (!trafficHourlyRange.value) {
|
const range = getTrafficTimeRange()
|
||||||
const now = new Date()
|
if (!range) return
|
||||||
trafficHourlyRange.value = [new Date(now.getTime() - 24 * 3600 * 1000), now]
|
|
||||||
}
|
|
||||||
trafficHourlyLoading.value = true
|
trafficHourlyLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getVmTrafficHourly({
|
const res = await getVmTrafficHourly({
|
||||||
service_id: serviceId.value,
|
service_id: serviceId.value,
|
||||||
host_id: detail.value.host_id,
|
host_id: detail.value.host_id,
|
||||||
vm_name: detail.value.name,
|
vm_name: detail.value.name,
|
||||||
start: new Date(trafficHourlyRange.value[0]).toISOString(),
|
start: range.startTime.toISOString(),
|
||||||
end_time: new Date(trafficHourlyRange.value[1]).toISOString()
|
end_time: range.endTime.toISOString()
|
||||||
})
|
})
|
||||||
const raw = res?.data?.data?.data
|
const raw = res?.data?.data?.data
|
||||||
trafficHourlyData.value = typeof raw === 'string' ? JSON.parse(raw) : (Array.isArray(raw) ? raw : [])
|
trafficHourlyData.value = typeof raw === 'string' ? JSON.parse(raw) : (Array.isArray(raw) ? raw : [])
|
||||||
|
|||||||
@@ -32,19 +32,34 @@
|
|||||||
<div class="toolbar-row">
|
<div class="toolbar-row">
|
||||||
<div class="toolbar-item">
|
<div class="toolbar-item">
|
||||||
<span class="toolbar-label">时间</span>
|
<span class="toolbar-label">时间</span>
|
||||||
<el-select v-model="timeRange" style="width: 140px" @change="handleRefresh">
|
<el-radio-group v-model="timeMode" size="small" @change="handleRefresh">
|
||||||
<el-option label="最近10分钟" :value="10" />
|
<el-radio-button label="relative">最近</el-radio-button>
|
||||||
<el-option label="最近30分钟" :value="30" />
|
<el-radio-button label="fixed">自定义</el-radio-button>
|
||||||
<el-option label="最近1小时" :value="60" />
|
</el-radio-group>
|
||||||
<el-option label="最近3小时" :value="180" />
|
<el-select v-if="timeMode === 'relative'" v-model="timeRange" style="width: 130px" @change="handleRefresh">
|
||||||
<el-option label="最近6小时" :value="360" />
|
<el-option label="10分钟" :value="10" />
|
||||||
<el-option label="最近12小时" :value="720" />
|
<el-option label="30分钟" :value="30" />
|
||||||
<el-option label="最近24小时" :value="1440" />
|
<el-option label="1小时" :value="60" />
|
||||||
|
<el-option label="3小时" :value="180" />
|
||||||
|
<el-option label="6小时" :value="360" />
|
||||||
|
<el-option label="12小时" :value="720" />
|
||||||
|
<el-option label="24小时" :value="1440" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-date-picker
|
||||||
|
v-else
|
||||||
|
v-model="fixedDateRange"
|
||||||
|
type="datetimerange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始"
|
||||||
|
end-placeholder="结束"
|
||||||
|
size="small"
|
||||||
|
style="width: 340px"
|
||||||
|
@change="handleRefresh"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-item">
|
<div class="toolbar-item">
|
||||||
<span class="toolbar-label">自动刷新</span>
|
<span class="toolbar-label">自动刷新</span>
|
||||||
<el-select v-model="autoRefreshInterval" style="width: 120px" @change="resetAutoRefresh">
|
<el-select v-model="autoRefreshInterval" style="width: 110px" @change="resetAutoRefresh">
|
||||||
<el-option label="关闭" :value="0" />
|
<el-option label="关闭" :value="0" />
|
||||||
<el-option label="10秒" :value="10" />
|
<el-option label="10秒" :value="10" />
|
||||||
<el-option label="30秒" :value="30" />
|
<el-option label="30秒" :value="30" />
|
||||||
@@ -91,7 +106,9 @@ const selectedMetrics = ref(['cpu', 'memory'])
|
|||||||
const metricsLoading = ref(false)
|
const metricsLoading = ref(false)
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const metricsDataMap = ref({})
|
const metricsDataMap = ref({})
|
||||||
|
const timeMode = ref('relative')
|
||||||
const timeRange = ref(60)
|
const timeRange = ref(60)
|
||||||
|
const fixedDateRange = ref(null)
|
||||||
const autoRefreshInterval = ref(0)
|
const autoRefreshInterval = ref(0)
|
||||||
let autoRefreshTimer = null
|
let autoRefreshTimer = null
|
||||||
|
|
||||||
@@ -169,11 +186,23 @@ const loadVmList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTimeRange = () => {
|
||||||
|
if (timeMode.value === 'relative') {
|
||||||
|
const endTime = new Date()
|
||||||
|
const startTime = new Date(endTime - timeRange.value * 60 * 1000)
|
||||||
|
return { startTime, endTime }
|
||||||
|
} else {
|
||||||
|
if (!fixedDateRange.value || fixedDateRange.value.length < 2) return null
|
||||||
|
return { startTime: new Date(fixedDateRange.value[0]), endTime: new Date(fixedDateRange.value[1]) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
if (!selectedVms.value.length) return
|
if (!selectedVms.value.length) return
|
||||||
|
const range = getTimeRange()
|
||||||
|
if (!range) { ElMessage.warning('请选择时间范围'); return }
|
||||||
metricsLoading.value = true
|
metricsLoading.value = true
|
||||||
const endTime = new Date()
|
const { startTime, endTime } = range
|
||||||
const startTime = new Date(endTime - timeRange.value * 60 * 1000)
|
|
||||||
const interval = calcInterval(endTime - startTime)
|
const interval = calcInterval(endTime - startTime)
|
||||||
|
|
||||||
const dataMap = {}
|
const dataMap = {}
|
||||||
@@ -208,7 +237,9 @@ const handleRefresh = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderCharts = () => {
|
const renderCharts = () => {
|
||||||
const showDate = timeRange.value >= 720
|
const range = getTimeRange()
|
||||||
|
const spanMs = range ? (range.endTime - range.startTime) : 0
|
||||||
|
const showDate = spanMs >= 12 * 3600 * 1000
|
||||||
const labelRotate = showDate ? 30 : 0
|
const labelRotate = showDate ? 30 : 0
|
||||||
|
|
||||||
for (const metric of selectedMetrics.value) {
|
for (const metric of selectedMetrics.value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user