修复 空数据时页面组件不显示
This commit is contained in:
@@ -10,7 +10,8 @@ VITE_USE_MOCK=false
|
|||||||
|
|
||||||
# API 基础URL
|
# API 基础URL
|
||||||
# VITE_API_BASE_URL=https://ops-api.apinb.com
|
# VITE_API_BASE_URL=https://ops-api.apinb.com
|
||||||
VITE_API_BASE_URL=http://127.0.0.1
|
# 开发环境走 Vite 同源代理,避免依赖本机 80/443 nginx
|
||||||
|
VITE_API_BASE_URL=
|
||||||
|
|
||||||
# Logs 本地调试地址(仅 logs 模块使用)
|
# Logs 本地调试地址(仅 logs 模块使用)
|
||||||
VITE_LOGS_API_BASE_URL=http://127.0.0.1:12440
|
VITE_LOGS_API_BASE_URL=http://127.0.0.1:12440
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { mergeConfig } from 'vite'
|
|||||||
// import eslint from 'vite-plugin-eslint'
|
// import eslint from 'vite-plugin-eslint'
|
||||||
import baseConfig from './vite.config.base'
|
import baseConfig from './vite.config.base'
|
||||||
|
|
||||||
|
const proxyTarget = (port: number) => ({
|
||||||
|
target: `http://127.0.0.1:${port}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
})
|
||||||
|
|
||||||
export default mergeConfig(
|
export default mergeConfig(
|
||||||
{
|
{
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
@@ -11,6 +16,31 @@ export default mergeConfig(
|
|||||||
fs: {
|
fs: {
|
||||||
strict: true,
|
strict: true,
|
||||||
},
|
},
|
||||||
|
proxy: {
|
||||||
|
'/rbac2': proxyTarget(10001),
|
||||||
|
'/Alert': proxyTarget(12427),
|
||||||
|
'/alert': proxyTarget(12427),
|
||||||
|
'/DC-Control': proxyTarget(3031),
|
||||||
|
'/dc-control': proxyTarget(3031),
|
||||||
|
'/dc-network': proxyTarget(12429),
|
||||||
|
'/DC-Hardware': proxyTarget(12450),
|
||||||
|
'/dc-hardware': proxyTarget(12450),
|
||||||
|
'/dc-host': proxyTarget(9030),
|
||||||
|
'/dc-middleware': proxyTarget(12428),
|
||||||
|
'/dc-database': proxyTarget(12580),
|
||||||
|
'/Feedback': proxyTarget(12432),
|
||||||
|
'/feedback': proxyTarget(12432),
|
||||||
|
'/Assets': proxyTarget(12430),
|
||||||
|
'/assets': proxyTarget(12430),
|
||||||
|
'/Logs': proxyTarget(12440),
|
||||||
|
'/logs': proxyTarget(12440),
|
||||||
|
'/Kb': proxyTarget(12434),
|
||||||
|
'/kb': proxyTarget(12434),
|
||||||
|
'/Mgt': proxyTarget(12436),
|
||||||
|
'/mgt': proxyTarget(12436),
|
||||||
|
'/Visual': proxyTarget(12438),
|
||||||
|
'/visual': proxyTarget(12438),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// eslint({
|
// eslint({
|
||||||
|
|||||||
@@ -162,6 +162,13 @@ export const fetchServerMetricsSummary = (serverIdentity: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 立即拉取一次主机指标并返回最新统计卡片 */
|
||||||
|
export const collectServerMetricsNow = (serverIdentity: string) => {
|
||||||
|
return request.post<{ code: number; details?: HostMetricsSummary; message?: string }>('/DC-Control/v1/servers/metrics/collect', {
|
||||||
|
server_identity: serverIdentity,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 近 N 小时网络收/发速率(Mbps,相邻采样字节差分) */
|
/** 近 N 小时网络收/发速率(Mbps,相邻采样字节差分) */
|
||||||
export const fetchServerNetworkTraffic = (serverIdentity: string, hours = 6) => {
|
export const fetchServerNetworkTraffic = (serverIdentity: string, hours = 6) => {
|
||||||
return request.get<{ code: number; details?: HostNetworkTrafficPayload; message?: string }>(
|
return request.get<{ code: number; details?: HostNetworkTrafficPayload; message?: string }>(
|
||||||
|
|||||||
@@ -120,6 +120,12 @@
|
|||||||
<icon-down />
|
<icon-down />
|
||||||
</a-button>
|
</a-button>
|
||||||
<template #content>
|
<template #content>
|
||||||
|
<a-doption :disabled="isCollecting(record) || !record.server_identity" @click="handleCollectNow(record)">
|
||||||
|
<template #icon>
|
||||||
|
<icon-refresh />
|
||||||
|
</template>
|
||||||
|
{{ isCollecting(record) ? '采集中' : '立即采集' }}
|
||||||
|
</a-doption>
|
||||||
<a-doption @click="handleQuickConfig(record)">
|
<a-doption @click="handleQuickConfig(record)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-settings />
|
<icon-settings />
|
||||||
@@ -200,13 +206,7 @@ import ServerFormDialog from './components/ServerFormDialog.vue'
|
|||||||
import QuickConfigDialog from './components/QuickConfigDialog.vue'
|
import QuickConfigDialog from './components/QuickConfigDialog.vue'
|
||||||
import HardwareDeviceConfigDialog from './components/HardwareDeviceConfigDialog.vue'
|
import HardwareDeviceConfigDialog from './components/HardwareDeviceConfigDialog.vue'
|
||||||
import { columns as columnsConfig } from './config/columns'
|
import { columns as columnsConfig } from './config/columns'
|
||||||
import { fetchServerList, deleteServer } from '@/api/ops/server'
|
import { fetchServerList, deleteServer, fetchServerMetricsSummary, collectServerMetricsNow, type HostMetricsSummary } from '@/api/ops/server'
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
// 创建独立的 axios 实例用于请求外部 agent,绕过全局拦截器
|
|
||||||
const agentAxios = axios.create({
|
|
||||||
timeout: 5000,
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -217,6 +217,7 @@ const formDialogVisible = ref(false)
|
|||||||
const quickConfigVisible = ref(false)
|
const quickConfigVisible = ref(false)
|
||||||
const hardwareConfigVisible = ref(false)
|
const hardwareConfigVisible = ref(false)
|
||||||
const currentRecord = ref<any>(null)
|
const currentRecord = ref<any>(null)
|
||||||
|
const collectingMap = reactive<Record<string, boolean>>({})
|
||||||
const formModel = ref({
|
const formModel = ref({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
collect_on: undefined as boolean | undefined,
|
collect_on: undefined as boolean | undefined,
|
||||||
@@ -394,6 +395,13 @@ const handleHardwareConfigSuccess = () => {
|
|||||||
fetchServers()
|
fetchServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCollectingKey = (record: any) => record.server_identity || String(record.id || '')
|
||||||
|
|
||||||
|
const isCollecting = (record: any) => {
|
||||||
|
const key = getCollectingKey(record)
|
||||||
|
return !!collectingMap[key]
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑服务器
|
// 编辑服务器
|
||||||
const handleEdit = (record: any) => {
|
const handleEdit = (record: any) => {
|
||||||
currentRecord.value = record
|
currentRecord.value = record
|
||||||
@@ -465,74 +473,86 @@ const handleDelete = async (record: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** dc-host `GET agent_config`(一般为 `/dc-host/stats`)返回裸 Metrics JSON */
|
const resetMetrics = (record: any) => {
|
||||||
// 获取所有服务器的监控指标
|
|
||||||
const getAllMetrics = async () => {
|
|
||||||
try {
|
|
||||||
// 遍历每个服务器记录
|
|
||||||
const metricsPromises = tableData.value.map(async (record) => {
|
|
||||||
// 检查是否有 agent_config 配置
|
|
||||||
if (record.agent_config) {
|
|
||||||
try {
|
|
||||||
// 从 agent_config 中解析 URL
|
|
||||||
let metricsUrl = record.agent_config
|
|
||||||
|
|
||||||
// 验证 URL 是否合法
|
|
||||||
try {
|
|
||||||
new URL(metricsUrl)
|
|
||||||
} catch (urlError) {
|
|
||||||
console.warn(`服务器 ${record.name} 的 agent_config 不是合法的 URL:`, metricsUrl)
|
|
||||||
// 设置默认值 0
|
|
||||||
record.cpu_info = { value: 0, total: '', used: '' }
|
record.cpu_info = { value: 0, total: '', used: '' }
|
||||||
record.memory_info = { value: 0, total: '', used: '' }
|
record.memory_info = { value: 0, total: '', used: '' }
|
||||||
record.disk_info = { value: 0, total: '', used: '' }
|
record.disk_info = { value: 0, total: '', used: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyMetricsSummary = (record: any, summary?: HostMetricsSummary) => {
|
||||||
|
if (!summary || !summary.has_data) {
|
||||||
|
resetMetrics(record)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 使用独立的 axios 实例请求外部 agent,绕过全局拦截器
|
|
||||||
const response = await agentAxios.get(metricsUrl)
|
|
||||||
console.log('获取指标数据:', response.data)
|
|
||||||
if (response.data) {
|
|
||||||
// 更新记录的监控数据
|
|
||||||
record.cpu_info = {
|
record.cpu_info = {
|
||||||
value: Number((response.data.cpu_usage || 0).toFixed(2)),
|
value: Number((summary.cpu?.usage_percent || 0).toFixed(2)),
|
||||||
total: response.data.cpu?.length ? `${response.data.cpu.length}核` : '',
|
total: summary.cpu?.logical_cores_total ? `${summary.cpu.logical_cores_total}核` : '',
|
||||||
used: '',
|
used: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
record.memory_info = {
|
record.memory_info = {
|
||||||
value: Number((response.data.mem_usage?.used_percent || 0).toFixed(2)),
|
value: Number((summary.memory?.used_percent || 0).toFixed(2)),
|
||||||
total: response.data.mem_usage?.total ? `${(response.data.mem_usage.total / 1024 / 1024 / 1024).toFixed(1)}GB` : '',
|
total: summary.memory?.total_bytes ? `${(summary.memory.total_bytes / 1024 / 1024 / 1024).toFixed(1)}GB` : '',
|
||||||
used: response.data.mem_usage?.used ? `${(response.data.mem_usage.used / 1024 / 1024 / 1024).toFixed(1)}GB` : '',
|
used: summary.memory?.used_bytes ? `${(summary.memory.used_bytes / 1024 / 1024 / 1024).toFixed(1)}GB` : '',
|
||||||
}
|
}
|
||||||
|
|
||||||
record.disk_info = {
|
record.disk_info = {
|
||||||
value: Number((response.data.disk_usage?.used_percent || 0).toFixed(2)),
|
value: Number((summary.disk_root?.used_percent || 0).toFixed(2)),
|
||||||
total: response.data.disk_usage?.total ? `${(response.data.disk_usage.total / 1024 / 1024 / 1024).toFixed(0)}GB` : '',
|
total: summary.disk_root?.total_bytes ? `${(summary.disk_root.total_bytes / 1024 / 1024 / 1024).toFixed(0)}GB` : '',
|
||||||
used: response.data.disk_usage?.used ? `${(response.data.disk_usage.used / 1024 / 1024 / 1024).toFixed(0)}GB` : '',
|
used: summary.disk_root?.used_bytes ? `${(summary.disk_root.used_bytes / 1024 / 1024 / 1024).toFixed(0)}GB` : '',
|
||||||
}
|
}
|
||||||
|
if (summary.timestamp) {
|
||||||
|
record.last_check_time = summary.timestamp
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 dc-control 获取所有服务器最新时序指标
|
||||||
|
const getAllMetrics = async () => {
|
||||||
|
try {
|
||||||
|
const metricsPromises = tableData.value.map(async (record) => {
|
||||||
|
if (!record.server_identity) {
|
||||||
|
resetMetrics(record)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetchServerMetricsSummary(record.server_identity)
|
||||||
|
applyMetricsSummary(record, response.details)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`获取服务器 ${record.name} 的监控指标失败:`, error)
|
console.warn(`获取服务器 ${record.name} 的监控指标失败:`, error)
|
||||||
// 初始化默认值
|
resetMetrics(record)
|
||||||
record.cpu_info = { value: 0, total: '', used: '' }
|
|
||||||
record.memory_info = { value: 0, total: '', used: '' }
|
|
||||||
record.disk_info = { value: 0, total: '', used: '' }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有配置 agent,设置默认值
|
|
||||||
record.cpu_info = { value: 0, total: '', used: '' }
|
|
||||||
record.memory_info = { value: 0, total: '', used: '' }
|
|
||||||
record.disk_info = { value: 0, total: '', used: '' }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 等待所有请求完成
|
|
||||||
await Promise.all(metricsPromises)
|
await Promise.all(metricsPromises)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取所有服务器监控指标失败:', error)
|
console.error('获取所有服务器监控指标失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCollectNow = async (record: any) => {
|
||||||
|
if (!record.server_identity) {
|
||||||
|
Message.warning('缺少服务器唯一标识,无法采集')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const key = getCollectingKey(record)
|
||||||
|
if (collectingMap[key]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
collectingMap[key] = true
|
||||||
|
try {
|
||||||
|
const response = await collectServerMetricsNow(record.server_identity)
|
||||||
|
if (response.code === 0) {
|
||||||
|
applyMetricsSummary(record, response.details)
|
||||||
|
Message.success('采集完成')
|
||||||
|
} else {
|
||||||
|
Message.error(response.message || '采集失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('立即采集失败:', error)
|
||||||
|
Message.error('采集失败')
|
||||||
|
} finally {
|
||||||
|
collectingMap[key] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchServers()
|
fetchServers()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user