feat
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<a-layout>
|
||||
<a-layout-sider
|
||||
v-if="renderMenu"
|
||||
v-show="!hideMenu && !route?.meta?.isNewTab && !route?.meta?.is_full"
|
||||
v-show="!route?.meta?.is_full"
|
||||
class="layout-sider"
|
||||
:breakpoint="'xl'"
|
||||
:collapsible="true"
|
||||
|
||||
@@ -2,44 +2,42 @@ import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const REMOTE: AppRouteRecordRaw = {
|
||||
path: '/dc',
|
||||
name: 'DC',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.dc',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-desktop',
|
||||
order: 99,
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'DCDetail',
|
||||
component: () => import('@/views/ops/pages/dc/detail/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.dc.detail',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
hideInMenu: true,
|
||||
is_full: true,
|
||||
isNewTab: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'remote',
|
||||
name: 'DCRemote',
|
||||
component: () => import('@/views/ops/pages/dc/remote/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.dc.remote',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
hideInMenu: true,
|
||||
is_full: true,
|
||||
isNewTab: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
// path: '/dc',
|
||||
// name: 'DC',
|
||||
// component: DEFAULT_LAYOUT,
|
||||
// meta: {
|
||||
// locale: 'menu.dc',
|
||||
// requiresAuth: true,
|
||||
// icon: 'icon-desktop',
|
||||
// order: 99,
|
||||
// hideInMenu: true,
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'detail',
|
||||
// name: 'DCDetail',
|
||||
// component: () => import('@/views/ops/pages/dc/detail/index.vue'),
|
||||
// meta: {
|
||||
// locale: 'menu.dc.detail',
|
||||
// requiresAuth: true,
|
||||
// roles: ['*'],
|
||||
// // is_full: true,
|
||||
// isNewTab: true,
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// path: 'remote',
|
||||
// name: 'DCRemote',
|
||||
// component: () => import('@/views/ops/pages/dc/remote/index.vue'),
|
||||
// meta: {
|
||||
// locale: 'menu.dc.remote',
|
||||
// requiresAuth: true,
|
||||
// roles: ['*'],
|
||||
// // is_full: true,
|
||||
// isNewTab: true,
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
}
|
||||
|
||||
export default REMOTE
|
||||
|
||||
@@ -446,7 +446,7 @@ const handleEdit = (record: any) => {
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 详情 - 跳转到独立页面
|
||||
// 详情 - 在当前窗口打开
|
||||
const handleDetail = (record: any) => {
|
||||
router.push({
|
||||
path: '/dc/detail',
|
||||
@@ -470,9 +470,9 @@ const handleRestart = (record: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 远程控制 - 跳转到独立页面
|
||||
// 远程控制 - 在新窗口打开
|
||||
const handleRemoteControl = (record: any) => {
|
||||
router.push({
|
||||
const url = router.resolve({
|
||||
path: '/dc/remote',
|
||||
query: {
|
||||
id: record.id,
|
||||
@@ -480,7 +480,8 @@ const handleRemoteControl = (record: any) => {
|
||||
ip: record.ip,
|
||||
status: record.status,
|
||||
},
|
||||
})
|
||||
}).href
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
// 表单提交成功
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<!-- 顶部导航栏 -->
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<a-button type="text" @click="goBack">
|
||||
<!-- <a-button type="text" @click="goBack">
|
||||
<template #icon><icon-left /></template>
|
||||
返回
|
||||
</a-button>
|
||||
<a-divider direction="vertical" />
|
||||
</a-button> -->
|
||||
<!-- <a-divider direction="vertical" /> -->
|
||||
<span class="server-name">{{ serverName }}</span>
|
||||
<a-tag :color="getStatusColor(serverStatus)" size="small">{{ getStatusText(serverStatus) }}</a-tag>
|
||||
</div>
|
||||
|
||||
@@ -148,6 +148,13 @@
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</search-table>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ServerFormDialog
|
||||
v-model:visible="formDialogVisible"
|
||||
:record="currentRecord"
|
||||
@success="handleFormSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -167,6 +174,7 @@ import {
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import { searchFormConfig } from './config/search-form'
|
||||
import ServerFormDialog from '../pc/components/ServerFormDialog.vue'
|
||||
import { columns as columnsConfig } from './config/columns'
|
||||
import {
|
||||
fetchServerList,
|
||||
@@ -382,6 +390,8 @@ const mockServerData = [
|
||||
// 状态管理
|
||||
const loading = ref(false)
|
||||
const tableData = ref<any[]>([])
|
||||
const formDialogVisible = ref(false)
|
||||
const currentRecord = ref<any>(null)
|
||||
const formModel = ref({
|
||||
keyword: '',
|
||||
datacenter_id: undefined,
|
||||
@@ -510,8 +520,19 @@ const handleRefresh = () => {
|
||||
|
||||
// 新增服务器
|
||||
const handleAdd = () => {
|
||||
Message.info('新增服务器功能待实现')
|
||||
// TODO: 实现新增服务器对话框
|
||||
currentRecord.value = null
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑服务器
|
||||
const handleEdit = (record: any) => {
|
||||
currentRecord.value = record
|
||||
formDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 表单提交成功
|
||||
const handleFormSuccess = () => {
|
||||
fetchServers()
|
||||
}
|
||||
|
||||
// 重启服务器
|
||||
@@ -525,7 +546,7 @@ const handleRestart = (record: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 查看详情 - 跳转到独立页面
|
||||
// 查看详情 - 在当前窗口打开
|
||||
const handleDetail = (record: any) => {
|
||||
router.push({
|
||||
path: '/dc/detail',
|
||||
@@ -538,15 +559,9 @@ const handleDetail = (record: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑服务器
|
||||
const handleEdit = (record: any) => {
|
||||
Message.info('编辑服务器功能待实现')
|
||||
// TODO: 实现编辑服务器对话框
|
||||
}
|
||||
|
||||
// 远程控制 - 跳转到独立页面
|
||||
// 远程控制 - 在新窗口打开
|
||||
const handleRemoteControl = (record: any) => {
|
||||
router.push({
|
||||
const url = router.resolve({
|
||||
path: '/dc/remote',
|
||||
query: {
|
||||
id: record.id,
|
||||
@@ -554,7 +569,8 @@ const handleRemoteControl = (record: any) => {
|
||||
ip: record.ip,
|
||||
status: record.status,
|
||||
},
|
||||
})
|
||||
}).href
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
// 删除服务器
|
||||
|
||||
@@ -1,127 +1,81 @@
|
||||
<template>
|
||||
<div class="network-device-status-panel">
|
||||
<!-- 查询表单 -->
|
||||
<a-form :model="formModel" layout="inline" class="search-form">
|
||||
<a-form-item label="服务标识" field="service_identities">
|
||||
<a-input
|
||||
v-model="formModel.service_identities"
|
||||
placeholder="多个标识,逗号分隔"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="指标名称" field="metric_names">
|
||||
<a-input
|
||||
v-model="formModel.metric_names"
|
||||
placeholder="多个指标名称,逗号分隔(可选)"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="聚合方式" field="aggregation">
|
||||
<a-select
|
||||
v-model="formModel.aggregation"
|
||||
placeholder="请选择聚合方式"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-option value="avg">平均值</a-option>
|
||||
<a-option value="max">最大值</a-option>
|
||||
<a-option value="min">最小值</a-option>
|
||||
<a-option value="sum">求和</a-option>
|
||||
<a-option value="count">计数</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="时间范围" field="timeRange">
|
||||
<a-range-picker
|
||||
v-model="formModel.timeRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 380px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" :loading="loading" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<search-table
|
||||
:form-model="formModel"
|
||||
:form-items="formItems"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
title="网络设备状态"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<!-- 时间范围选择器插槽 -->
|
||||
<template #form-items>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="时间范围" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
|
||||
<a-range-picker
|
||||
v-model="formModel.timeRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 380px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
|
||||
<!-- 结果表格 -->
|
||||
<a-table
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
stripe
|
||||
class="result-table"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column title="服务标识" data-index="service_identity" width="200" fixed="left" />
|
||||
<a-table-column title="服务器标识" data-index="server_identity" width="180" />
|
||||
<a-table-column title="名称" data-index="name" width="150" />
|
||||
<a-table-column title="类型" data-index="type" width="120" />
|
||||
<a-table-column title="主机" data-index="host" width="150" />
|
||||
<a-table-column title="状态" data-index="status" width="100">
|
||||
<template #cell="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="响应时间" data-index="response_time" width="120">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ formatResponseTime(record.response_time) }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="运行时间" data-index="uptime" width="120">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ formatUptime(record.uptime) }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="启用" data-index="enabled" width="80">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ record.enabled ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="最后检查时间" data-index="last_check_time" width="180">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ formatTime(record.last_check_time) }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="指标数据" width="300">
|
||||
<template #cell="{ record }">
|
||||
<div v-if="record.metrics && Object.keys(record.metrics).length > 0" class="metrics-cell">
|
||||
<div v-for="(value, key) in record.metrics" :key="key" class="metric-item">
|
||||
<span class="metric-name">{{ key }}:</span>
|
||||
<span class="metric-value">{{ formatMetricValue(value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<a-empty v-if="!loading && tableData.length === 0" description="暂无数据" />
|
||||
</div>
|
||||
<!-- 响应时间 -->
|
||||
<template #response_time="{ record }">
|
||||
{{ formatResponseTime(record.response_time) }}
|
||||
</template>
|
||||
|
||||
<!-- 运行时间 -->
|
||||
<template #uptime="{ record }">
|
||||
{{ formatUptime(record.uptime) }}
|
||||
</template>
|
||||
|
||||
<!-- 启用 -->
|
||||
<template #enabled="{ record }">
|
||||
<span>{{ record.enabled ? '是' : '否' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 最后检查时间 -->
|
||||
<template #last_check_time="{ record }">
|
||||
{{ formatTime(record.last_check_time) }}
|
||||
</template>
|
||||
|
||||
<!-- 指标数据 -->
|
||||
<template #metrics="{ record }">
|
||||
<div v-if="record.metrics && Object.keys(record.metrics).length > 0" class="metrics-cell">
|
||||
<div v-for="(value, key) in record.metrics" :key="key" class="metric-item">
|
||||
<span class="metric-name">{{ key }}:</span>
|
||||
<span class="metric-value">{{ formatMetricValue(value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</search-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import { fetchNetworkDeviceStatus } from '@/api/ops/report'
|
||||
|
||||
// 表单模型
|
||||
const formModel = reactive({
|
||||
const formModel = ref({
|
||||
service_identities: '',
|
||||
metric_names: '',
|
||||
aggregation: 'avg',
|
||||
@@ -134,6 +88,111 @@ const loading = ref(false)
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
// 表单项配置
|
||||
const formItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
field: 'service_identities',
|
||||
label: '服务标识',
|
||||
type: 'input',
|
||||
placeholder: '多个标识,逗号分隔',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'metric_names',
|
||||
label: '指标名称',
|
||||
type: 'input',
|
||||
placeholder: '多个指标名称,逗号分隔(可选)',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'aggregation',
|
||||
label: '聚合方式',
|
||||
type: 'select',
|
||||
placeholder: '请选择聚合方式',
|
||||
options: [
|
||||
{ label: '平均值', value: 'avg' },
|
||||
{ label: '最大值', value: 'max' },
|
||||
{ label: '最小值', value: 'min' },
|
||||
{ label: '求和', value: 'sum' },
|
||||
{ label: '计数', value: 'count' },
|
||||
],
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
])
|
||||
|
||||
// 表格列配置
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: '服务标识',
|
||||
dataIndex: 'service_identity',
|
||||
width: 200,
|
||||
fixed: 'left' as const,
|
||||
},
|
||||
{
|
||||
title: '服务器标识',
|
||||
dataIndex: 'server_identity',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '主机',
|
||||
dataIndex: 'host',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
slotName: 'status',
|
||||
},
|
||||
{
|
||||
title: '响应时间',
|
||||
dataIndex: 'response_time',
|
||||
width: 120,
|
||||
slotName: 'response_time',
|
||||
},
|
||||
{
|
||||
title: '运行时间',
|
||||
dataIndex: 'uptime',
|
||||
width: 120,
|
||||
slotName: 'uptime',
|
||||
},
|
||||
{
|
||||
title: '启用',
|
||||
dataIndex: 'enabled',
|
||||
width: 80,
|
||||
slotName: 'enabled',
|
||||
},
|
||||
{
|
||||
title: '最后检查时间',
|
||||
dataIndex: 'last_check_time',
|
||||
width: 180,
|
||||
slotName: 'last_check_time',
|
||||
},
|
||||
{
|
||||
title: '指标数据',
|
||||
dataIndex: 'metrics',
|
||||
width: 300,
|
||||
slotName: 'metrics',
|
||||
},
|
||||
])
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
if (!status) return 'gray'
|
||||
@@ -193,64 +252,91 @@ const formatMetricValue = (metric: any) => {
|
||||
// 查询
|
||||
const handleSearch = async () => {
|
||||
// 验证必填项
|
||||
if (!formModel.service_identities) {
|
||||
if (!formModel.value.service_identities) {
|
||||
Message.warning('请输入服务标识')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果填写了指标名称,必须同时填写时间范围
|
||||
if (formModel.metric_names && (!formModel.timeRange || formModel.timeRange.length !== 2)) {
|
||||
if (formModel.value.metric_names && (!formModel.value.timeRange || formModel.value.timeRange.length !== 2)) {
|
||||
Message.warning('填写指标名称时必须选择时间范围')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果填写了时间范围,必须同时填写指标名称
|
||||
if (formModel.timeRange && formModel.timeRange.length === 2 && !formModel.metric_names) {
|
||||
if (formModel.value.timeRange && formModel.value.timeRange.length === 2 && !formModel.value.metric_names) {
|
||||
Message.warning('选择时间范围时必须填写指标名称')
|
||||
return
|
||||
}
|
||||
|
||||
pagination.current = 1
|
||||
await fetchTableData()
|
||||
}
|
||||
|
||||
// 获取表格数据
|
||||
const fetchTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const params: any = {
|
||||
service_identities: formModel.service_identities,
|
||||
service_identities: formModel.value.service_identities,
|
||||
}
|
||||
|
||||
if (formModel.metric_names) {
|
||||
params.metric_names = formModel.metric_names
|
||||
params.aggregation = formModel.aggregation
|
||||
params.start_time = formModel.timeRange[0]
|
||||
params.end_time = formModel.timeRange[1]
|
||||
if (formModel.value.metric_names) {
|
||||
params.metric_names = formModel.value.metric_names
|
||||
params.aggregation = formModel.value.aggregation
|
||||
params.start_time = formModel.value.timeRange[0]
|
||||
params.end_time = formModel.value.timeRange[1]
|
||||
}
|
||||
|
||||
const res = await fetchNetworkDeviceStatus(params)
|
||||
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data?.data || []
|
||||
pagination.total = res.data?.total || 0
|
||||
if (tableData.value.length === 0) {
|
||||
Message.info('未查询到数据')
|
||||
}
|
||||
} else {
|
||||
Message.error(res.message || '查询失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('查询失败:', error)
|
||||
Message.error(error.message || '查询失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表单模型更新
|
||||
const handleFormModelUpdate = (value: any) => {
|
||||
formModel.value = {
|
||||
...formModel.value,
|
||||
...value,
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
formModel.service_identities = ''
|
||||
formModel.metric_names = ''
|
||||
formModel.aggregation = 'avg'
|
||||
formModel.timeRange = []
|
||||
formModel.value = {
|
||||
service_identities: '',
|
||||
metric_names: '',
|
||||
aggregation: 'avg',
|
||||
timeRange: [],
|
||||
}
|
||||
pagination.current = 1
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
fetchTableData()
|
||||
Message.success('数据已刷新')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -261,39 +347,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.network-device-status-panel {
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background-color: var(--color-fill-2);
|
||||
border-radius: 4px;
|
||||
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
.metrics-cell {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.result-table {
|
||||
background-color: #fff;
|
||||
.metric-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
.metrics-cell {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.metric-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
.metric-name {
|
||||
color: var(--color-text-2);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.metric-name {
|
||||
color: var(--color-text-2);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,71 @@
|
||||
<template>
|
||||
<div class="server-status-panel">
|
||||
<!-- 查询表单 -->
|
||||
<a-form :model="formModel" layout="inline" class="search-form">
|
||||
<a-form-item label="服务器标识" field="server_identities">
|
||||
<a-input
|
||||
v-model="formModel.server_identities"
|
||||
placeholder="多个标识,逗号分隔"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="指标名称" field="metric_names">
|
||||
<a-input
|
||||
v-model="formModel.metric_names"
|
||||
placeholder="多个指标名称,逗号分隔(可选)"
|
||||
style="width: 250px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="聚合方式" field="aggregation">
|
||||
<a-select
|
||||
v-model="formModel.aggregation"
|
||||
placeholder="请选择聚合方式"
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-option value="avg">平均值</a-option>
|
||||
<a-option value="max">最大值</a-option>
|
||||
<a-option value="min">最小值</a-option>
|
||||
<a-option value="sum">求和</a-option>
|
||||
<a-option value="count">计数</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="时间范围" field="timeRange">
|
||||
<a-range-picker
|
||||
v-model="formModel.timeRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 380px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" :loading="loading" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<search-table
|
||||
:form-model="formModel"
|
||||
:form-items="formItems"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
title="服务器状态"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<!-- 时间范围选择器插槽 -->
|
||||
<template #form-items>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="时间范围" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
|
||||
<a-range-picker
|
||||
v-model="formModel.timeRange"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 380px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
|
||||
<!-- 结果表格 -->
|
||||
<a-table
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
stripe
|
||||
class="result-table"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column title="服务器标识" data-index="server_identity" width="180" fixed="left" />
|
||||
<a-table-column title="名称" data-index="name" width="150" />
|
||||
<a-table-column title="主机" data-index="host" width="150" />
|
||||
<a-table-column title="IP 地址" data-index="ip_address" width="150" />
|
||||
<a-table-column title="状态" data-index="status" width="100">
|
||||
<template #cell="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="启用" data-index="enable" width="80">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ record.enable ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="最后检查时间" data-index="last_check_time" width="180">
|
||||
<template #cell="{ record }">
|
||||
<span>{{ formatTime(record.last_check_time) }}</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="指标数据" width="300">
|
||||
<template #cell="{ record }">
|
||||
<div v-if="record.metrics && Object.keys(record.metrics).length > 0" class="metrics-cell">
|
||||
<div v-for="(value, key) in record.metrics" :key="key" class="metric-item">
|
||||
<span class="metric-name">{{ key }}:</span>
|
||||
<span class="metric-value">{{ formatMetricValue(value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ record.status || '-' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<a-empty v-if="!loading && tableData.length === 0" description="暂无数据" />
|
||||
</div>
|
||||
<!-- 启用 -->
|
||||
<template #enable="{ record }">
|
||||
<span>{{ record.enable ? '是' : '否' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 最后检查时间 -->
|
||||
<template #last_check_time="{ record }">
|
||||
{{ formatTime(record.last_check_time) }}
|
||||
</template>
|
||||
|
||||
<!-- 指标数据 -->
|
||||
<template #metrics="{ record }">
|
||||
<div v-if="record.metrics && Object.keys(record.metrics).length > 0" class="metrics-cell">
|
||||
<div v-for="(value, key) in record.metrics" :key="key" class="metric-item">
|
||||
<span class="metric-name">{{ key }}:</span>
|
||||
<span class="metric-value">{{ formatMetricValue(value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</search-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import { fetchServerStatus } from '@/api/ops/report'
|
||||
|
||||
// 表单模型
|
||||
const formModel = reactive({
|
||||
const formModel = ref({
|
||||
server_identities: '',
|
||||
metric_names: '',
|
||||
aggregation: 'avg',
|
||||
@@ -123,6 +78,94 @@ const loading = ref(false)
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
// 表单项配置
|
||||
const formItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
field: 'server_identities',
|
||||
label: '服务器标识',
|
||||
type: 'input',
|
||||
placeholder: '多个标识,逗号分隔',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'metric_names',
|
||||
label: '指标名称',
|
||||
type: 'input',
|
||||
placeholder: '多个指标名称,逗号分隔(可选)',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
{
|
||||
field: 'aggregation',
|
||||
label: '聚合方式',
|
||||
type: 'select',
|
||||
placeholder: '请选择聚合方式',
|
||||
options: [
|
||||
{ label: '平均值', value: 'avg' },
|
||||
{ label: '最大值', value: 'max' },
|
||||
{ label: '最小值', value: 'min' },
|
||||
{ label: '求和', value: 'sum' },
|
||||
{ label: '计数', value: 'count' },
|
||||
],
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
])
|
||||
|
||||
// 表格列配置
|
||||
const columns = computed(() => [
|
||||
{
|
||||
title: '服务器标识',
|
||||
dataIndex: 'server_identity',
|
||||
width: 180,
|
||||
fixed: 'left' as const,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '主机',
|
||||
dataIndex: 'host',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'IP 地址',
|
||||
dataIndex: 'ip_address',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
slotName: 'status',
|
||||
},
|
||||
{
|
||||
title: '启用',
|
||||
dataIndex: 'enable',
|
||||
width: 80,
|
||||
slotName: 'enable',
|
||||
},
|
||||
{
|
||||
title: '最后检查时间',
|
||||
dataIndex: 'last_check_time',
|
||||
width: 180,
|
||||
slotName: 'last_check_time',
|
||||
},
|
||||
{
|
||||
title: '指标数据',
|
||||
dataIndex: 'metrics',
|
||||
width: 300,
|
||||
slotName: 'metrics',
|
||||
},
|
||||
])
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
if (!status) return 'gray'
|
||||
@@ -159,64 +202,91 @@ const formatMetricValue = (metric: any) => {
|
||||
// 查询
|
||||
const handleSearch = async () => {
|
||||
// 验证必填项
|
||||
if (!formModel.server_identities) {
|
||||
if (!formModel.value.server_identities) {
|
||||
Message.warning('请输入服务器标识')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果填写了指标名称,必须同时填写时间范围
|
||||
if (formModel.metric_names && (!formModel.timeRange || formModel.timeRange.length !== 2)) {
|
||||
if (formModel.value.metric_names && (!formModel.value.timeRange || formModel.value.timeRange.length !== 2)) {
|
||||
Message.warning('填写指标名称时必须选择时间范围')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果填写了时间范围,必须同时填写指标名称
|
||||
if (formModel.timeRange && formModel.timeRange.length === 2 && !formModel.metric_names) {
|
||||
if (formModel.value.timeRange && formModel.value.timeRange.length === 2 && !formModel.value.metric_names) {
|
||||
Message.warning('选择时间范围时必须填写指标名称')
|
||||
return
|
||||
}
|
||||
|
||||
pagination.current = 1
|
||||
await fetchTableData()
|
||||
}
|
||||
|
||||
// 获取表格数据
|
||||
const fetchTableData = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const params: any = {
|
||||
server_identities: formModel.server_identities,
|
||||
server_identities: formModel.value.server_identities,
|
||||
}
|
||||
|
||||
if (formModel.metric_names) {
|
||||
params.metric_names = formModel.metric_names
|
||||
params.aggregation = formModel.aggregation
|
||||
params.start_time = formModel.timeRange[0]
|
||||
params.end_time = formModel.timeRange[1]
|
||||
if (formModel.value.metric_names) {
|
||||
params.metric_names = formModel.value.metric_names
|
||||
params.aggregation = formModel.value.aggregation
|
||||
params.start_time = formModel.value.timeRange[0]
|
||||
params.end_time = formModel.value.timeRange[1]
|
||||
}
|
||||
|
||||
const res = await fetchServerStatus(params)
|
||||
|
||||
if (res.code === 0) {
|
||||
tableData.value = res.data?.data || []
|
||||
pagination.total = res.data?.total || 0
|
||||
if (tableData.value.length === 0) {
|
||||
Message.info('未查询到数据')
|
||||
}
|
||||
} else {
|
||||
Message.error(res.message || '查询失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('查询失败:', error)
|
||||
Message.error(error.message || '查询失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表单模型更新
|
||||
const handleFormModelUpdate = (value: any) => {
|
||||
formModel.value = {
|
||||
...formModel.value,
|
||||
...value,
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
formModel.server_identities = ''
|
||||
formModel.metric_names = ''
|
||||
formModel.aggregation = 'avg'
|
||||
formModel.timeRange = []
|
||||
formModel.value = {
|
||||
server_identities: '',
|
||||
metric_names: '',
|
||||
aggregation: 'avg',
|
||||
timeRange: [],
|
||||
}
|
||||
pagination.current = 1
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = () => {
|
||||
fetchTableData()
|
||||
Message.success('数据已刷新')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -227,39 +297,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.server-status-panel {
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background-color: var(--color-fill-2);
|
||||
border-radius: 4px;
|
||||
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
.metrics-cell {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.result-table {
|
||||
background-color: #fff;
|
||||
.metric-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
.metrics-cell {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.metric-item {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
.metric-name {
|
||||
color: var(--color-text-2);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.metric-name {
|
||||
color: var(--color-text-2);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import MetricsSummaryPanel from './components/MetricsSummaryPanel.vue'
|
||||
import TrafficSummaryPanel from './components/TrafficSummaryPanel.vue'
|
||||
import TrafficTrendPanel from './components/TrafficTrendPanel.vue'
|
||||
import ServerStatusPanel from './components/ServerStatusPanel.vue'
|
||||
import NetworkStatusPanel from './components/NetworkStatusPanel.vue'
|
||||
import NetworkStatusPanel from './components/NetworkDeviceStatusPanel.vue'
|
||||
|
||||
// 当前激活的标签页
|
||||
const activeTab = ref('metrics-topn')
|
||||
|
||||
Reference in New Issue
Block a user