feat(监控): 实现服务器监控页面
- 新增服务器监控主页面,展示系统基础信息 - 实现CPU、内存、JVM、磁盘使用情况展示 - 新增StatCard统计卡片组件,支持图标和趋势显示 - 采用响应式网格布局,适配不同屏幕尺寸 - 集成系统信息实时监控和数据刷新功能
This commit is contained in:
parent
a6ce521255
commit
19180e5702
182
src/views/monitor/server/components/StatCard.vue
Normal file
182
src/views/monitor/server/components/StatCard.vue
Normal file
@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<n-card class="stat-card" :style="cardStyle" hoverable>
|
||||
<!-- 主要内容区域 -->
|
||||
<n-space justify="space-between" align="center" class="mb-2">
|
||||
<!-- 左侧数据区域 -->
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="stat-value">
|
||||
{{ value }}
|
||||
</div>
|
||||
<div class="stat-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧图标区域 -->
|
||||
<div class="stat-icon" :style="iconStyle">
|
||||
<n-icon :size="28">
|
||||
<IconParkOutlineServer v-if="icon === 'server'" />
|
||||
<IconParkOutlineComputer v-else-if="icon === 'system'" />
|
||||
<IconParkOutlineNetworkTree v-else-if="icon === 'network'" />
|
||||
<IconParkOutlineChip v-else-if="icon === 'chip'" />
|
||||
<IconParkOutlineCpu v-else-if="icon === 'cpu'" />
|
||||
<IconParkOutlineData v-else-if="icon === 'memory'" />
|
||||
<IconParkOutlineCode v-else-if="icon === 'java'" />
|
||||
<IconParkOutlineServer v-else />
|
||||
</n-icon>
|
||||
</div>
|
||||
</n-space>
|
||||
|
||||
<!-- 底部信息区域 -->
|
||||
<div v-if="trend || extraInfo" class="stat-footer">
|
||||
<n-space justify="space-between" align="center">
|
||||
<div class="stat-extra">
|
||||
{{ extraInfo }}
|
||||
</div>
|
||||
<div v-if="trend" class="stat-trend" :class="trendClass">
|
||||
<n-icon :size="12" class="trend-icon">
|
||||
<IconParkOutlineUp v-if="trendUp" />
|
||||
<IconParkOutlineDown v-else />
|
||||
</n-icon>
|
||||
<span class="trend-text">{{ trend }}</span>
|
||||
</div>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 定义Props接口
|
||||
interface StatCardProps {
|
||||
title: string
|
||||
value: string | number
|
||||
subtitle: string
|
||||
icon?: string
|
||||
color?: string
|
||||
trend?: string
|
||||
trendUp?: boolean
|
||||
extraInfo?: string
|
||||
}
|
||||
|
||||
// 定义Props
|
||||
const props = withDefaults(defineProps<StatCardProps>(), {
|
||||
icon: 'server',
|
||||
color: '#18a058',
|
||||
trendUp: true,
|
||||
})
|
||||
|
||||
// 卡片样式
|
||||
const cardStyle = computed(() => ({
|
||||
background:
|
||||
'linear-gradient(135deg, var(--card-color) 0%, rgba(255, 255, 255, 0.6) 100%)',
|
||||
border: '1px solid var(--border-color)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.05)',
|
||||
transition: 'all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
||||
padding: '18px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
minHeight: '140px',
|
||||
}))
|
||||
|
||||
// 图标样式
|
||||
const iconStyle = computed(() => ({
|
||||
color: props.color,
|
||||
background: `${props.color}12`,
|
||||
borderRadius: '10px',
|
||||
padding: '10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}))
|
||||
|
||||
// 趋势样式类
|
||||
const trendClass = computed(() => ({
|
||||
'trend-up': props.trendUp,
|
||||
'trend-down': !props.trendUp,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card {
|
||||
height: 100%;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color-3);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-color-1);
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.2;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.stat-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-color-3);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-footer {
|
||||
margin-top: 12px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.stat-extra {
|
||||
font-size: 12px;
|
||||
color: var(--text-color-3);
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.trend-up {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.trend-down {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.trend-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trend-text {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
327
src/views/monitor/server/index.vue
Normal file
327
src/views/monitor/server/index.vue
Normal file
@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div class="server-monitor-container">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header mb-6">
|
||||
<div class="header-left">
|
||||
<h2 class="page-title">
|
||||
服务监控
|
||||
</h2>
|
||||
<p class="page-subtitle">
|
||||
实时监控服务器运行状态和系统资源使用情况
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<n-button type="primary" :loading="loading" @click="refreshData">
|
||||
<template #icon>
|
||||
<n-icon><IconParkOutlineRefresh /></n-icon>
|
||||
</template>
|
||||
刷新数据
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统信息统计卡片 -->
|
||||
<n-grid :x-gap="16" :y-gap="16" class="mb-6">
|
||||
<n-gi :span="6">
|
||||
<StatCard
|
||||
title="服务器名称"
|
||||
:value="serverData?.sys?.computerName || 'N/A'"
|
||||
subtitle="主机名"
|
||||
icon="server"
|
||||
color="#18a058"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<StatCard
|
||||
title="操作系统"
|
||||
:value="serverData?.sys?.osName || 'N/A'"
|
||||
subtitle="系统版本"
|
||||
icon="system"
|
||||
color="#2080f0"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<StatCard
|
||||
title="服务器IP"
|
||||
:value="serverData?.sys?.computerIp || 'N/A'"
|
||||
subtitle="主机地址"
|
||||
icon="network"
|
||||
color="#f0a020"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<StatCard
|
||||
title="系统架构"
|
||||
:value="serverData?.sys?.osArch || 'N/A'"
|
||||
subtitle="处理器架构"
|
||||
icon="chip"
|
||||
color="#d03050"
|
||||
/>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<!-- CPU和内存信息 -->
|
||||
<n-grid :x-gap="16" :y-gap="16" class="mb-6">
|
||||
<n-gi :span="8">
|
||||
<StatCard
|
||||
title="CPU使用率"
|
||||
:value="formatPercentage(serverData?.cpu?.cpuUsage)"
|
||||
subtitle="处理器负载"
|
||||
icon="cpu"
|
||||
color="#18a058"
|
||||
:trend="formatPercentage(serverData?.cpu?.cpuUsage)"
|
||||
:trend-up="(serverData?.cpu?.cpuUsage || 0) < 80"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="8">
|
||||
<StatCard
|
||||
title="内存使用率"
|
||||
:value="formatPercentage(serverData?.mem?.usage)"
|
||||
subtitle="内存负载"
|
||||
icon="memory"
|
||||
color="#2080f0"
|
||||
:trend="formatPercentage(serverData?.mem?.usage)"
|
||||
:trend-up="(serverData?.mem?.usage || 0) < 80"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="8">
|
||||
<StatCard
|
||||
title="JVM使用率"
|
||||
:value="formatPercentage(serverData?.jvm?.usage)"
|
||||
subtitle="Java虚拟机"
|
||||
icon="java"
|
||||
color="#f0a020"
|
||||
:trend="formatPercentage(serverData?.jvm?.usage)"
|
||||
:trend-up="(serverData?.jvm?.usage || 0) < 80"
|
||||
/>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<!-- Java环境信息 -->
|
||||
<n-card title="Java环境信息" class="mb-6">
|
||||
<template #header-extra>
|
||||
<n-icon><IconParkOutlineCode /></n-icon>
|
||||
</template>
|
||||
<n-descriptions :column="3" label-placement="left" :label-style="{ width: '120px' }">
|
||||
<n-descriptions-item label="Java版本">
|
||||
{{ serverData?.jvm?.version || 'N/A' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="启动时间">
|
||||
{{ serverData?.jvm?.startTime || 'N/A' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="运行时间">
|
||||
{{ serverData?.jvm?.runTime || 'N/A' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="安装路径">
|
||||
{{ serverData?.jvm?.home || 'N/A' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="项目路径">
|
||||
{{ serverData?.sys?.userDir || 'N/A' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="运行参数">
|
||||
<n-tag type="info" size="small">
|
||||
-Djava.version={{ serverData?.jvm?.version || 'N/A' }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<!-- JVM内存详情 -->
|
||||
<n-divider>JVM内存详情</n-divider>
|
||||
<n-space>
|
||||
<n-statistic label="总内存" :value="serverData?.jvm?.totalStr || '0 MB'" />
|
||||
<n-statistic label="已用内存" :value="serverData?.jvm?.usedStr || '0 MB'" />
|
||||
<n-statistic label="剩余内存" :value="serverData?.jvm?.freeStr || '0 MB'" />
|
||||
<n-statistic label="最大内存" :value="serverData?.jvm?.maxStr || '0 MB'" />
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<!-- 磁盘状态 -->
|
||||
<n-card title="磁盘状态">
|
||||
<template #header-extra>
|
||||
<n-icon><IconParkOutlineHdd /></n-icon>
|
||||
</template>
|
||||
<n-data-table
|
||||
:columns="diskColumns"
|
||||
:data="serverData?.sysFiles || []"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { coiMsgError, coiMsgSuccess } from '@/utils/coi'
|
||||
import { getServerInformation } from '@/service/api/monitor/server'
|
||||
import type { ServerVo, SysFileVo } from '@/service/api/monitor/server'
|
||||
import StatCard from './components/StatCard.vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const serverData = ref<ServerVo>()
|
||||
|
||||
// 磁盘状态表格列定义
|
||||
const diskColumns: DataTableColumns<SysFileVo> = [
|
||||
{
|
||||
title: '盘符路径',
|
||||
key: 'dirName',
|
||||
width: 120,
|
||||
ellipsis: {
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '文件系统',
|
||||
key: 'sysTypeName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '盘符类型',
|
||||
key: 'typeName',
|
||||
width: 200,
|
||||
ellipsis: {
|
||||
tooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '总大小',
|
||||
key: 'total',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '可用大小',
|
||||
key: 'free',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '已用大小',
|
||||
key: 'used',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: '已用百分比',
|
||||
key: 'usage',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (row) => {
|
||||
const usage = row.usage || 0
|
||||
const color = usage > 80 ? '#d03050' : usage > 60 ? '#f0a020' : '#18a058'
|
||||
return h('div', { style: { color } }, `${usage.toFixed(1)}%`)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 格式化百分比
|
||||
function formatPercentage(value?: number): string {
|
||||
if (value === undefined || value === null)
|
||||
return '0.0%'
|
||||
return `${value.toFixed(1)}%`
|
||||
}
|
||||
|
||||
// 获取服务器监控数据
|
||||
async function fetchServerData() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data, isSuccess } = await getServerInformation()
|
||||
|
||||
if (isSuccess && data) {
|
||||
serverData.value = data
|
||||
}
|
||||
else {
|
||||
coiMsgError('获取服务器监控数据失败')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取服务器监控数据异常:', error)
|
||||
coiMsgError('获取服务器监控数据异常')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
async function refreshData() {
|
||||
await fetchServerData()
|
||||
coiMsgSuccess('数据刷新成功')
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchServerData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.server-monitor-container {
|
||||
padding: 20px;
|
||||
background-color: var(--body-color);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color-1);
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-color-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
:deep(.n-card) {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:deep(.n-card .n-card__header) {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
:deep(.n-card .n-card__content) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:deep(.n-descriptions .n-descriptions-item-label) {
|
||||
font-weight: 500;
|
||||
color: var(--text-color-2);
|
||||
}
|
||||
|
||||
:deep(.n-divider) {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
:deep(.n-statistic) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.n-data-table) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user