feat: add server-status page

This commit is contained in:
chansee97 2025-09-17 23:01:28 +08:00
parent ecb880b177
commit c65bde0c4a
3 changed files with 274 additions and 2 deletions

91
src/api/monitor/server.ts Normal file
View File

@ -0,0 +1,91 @@
import { request } from '@/utils/alova'
export interface ServerStatus {
/** 主机名例如DESKTOP-XXXX */
hostname: string
/** 操作系统信息 */
os: {
/** 平台例如win32、linux、darwin */
platform: string
/** 架构例如x64、arm64 */
arch: string
/** 系统版本号例如10.0.26100 */
release: string
/** 系统运行时长,已格式化,例如:"1d 2h 3m 4s" */
uptime: string
/** 内核类型例如Windows_NT、Linux、Darwin */
type?: string
/** 内核版本(可能为空,平台相关) */
kernelVersion?: string
/**
* Unix
* - low: 负载较轻
* - medium: 负载中等
* - high: 负载较高
* - overload: 过载
* - Windows undefined
*/
load?: 'low' | 'medium' | 'high' | 'overload'
}
/** CPU 概览 */
cpu: {
/** CPU 型号名称 */
model: string
/** CPU 核心数(含单位),例如:"12 cores" */
cores: string
/** 主频(含单位),例如:"3700 MHz" */
speed: string
/** 逻辑核心数量(数值) */
logicalCores?: number
/** CPU 用户使用率(百分比) */
userUsage: number
/** CPU 系统使用率(百分比) */
systemUsage: number
/** CPU 当前空闲率(百分比) */
idle: number
}
/** 内存概览 */
memory: {
/** 总内存(含单位),例如:"31.9 GB" */
total: string
/** 已用内存(含单位),例如:"11.3 GB" */
used: string
/** 空闲内存(含单位),例如:"20.7 GB" */
free: string
/** 内存占用百分比(含%),例如:"35.32" */
usedPercent: number
}
/** 网络信息 */
network: {
/** 主要 IPv4 地址(首个非内网 IPv4可能为空 */
primaryIPv4?: string
/** 网卡接口数量(字符串),例如:"3" */
interfaceCount: string
/** 网卡接口简单列表 */
interfaces?: Array<{
/** 网卡名称(接口名) */
name: string
/** 物理地址(可能为空) */
mac?: string
/** IPv4 地址列表 */
ipv4: string[]
/** IPv6 地址列表 */
ipv6: string[]
/** 是否为内网/回环接口 */
internal: boolean
}>
}
/** 进程信息 */
process: {
/** 进程 ID字符串 */
pid: string
/** Node.js 版本,例如:"v18.19.0" */
nodeVersion: string
/** 进程运行时长,已格式化,例如:"14m 8s" */
uptime: string
}
}
export function getServerStatus() {
return request.Get<Api.Response<ServerStatus>>('/server-status')
}

View File

@ -9,8 +9,8 @@
* normalizeSizeUnits(1048576)
* ```
*/
export function normalizeSizeUnits(bytes: number): string {
if (bytes === 0)
export function normalizeSizeUnits(bytes?: number): string {
if (bytes === 0 || !bytes)
return '0 bytes'
const units = ['bytes', 'KB', 'MB', 'GB']

View File

@ -0,0 +1,181 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getServerStatus } from '@/api/monitor/server'
import type { ServerStatus } from '@/api/monitor/server'
const loading = ref(false)
const data = ref<ServerStatus | null>(null)
function loadColor(load?: ServerStatus['os']['load']): NaiveUI.ThemeColor {
switch (load) {
case 'low':
return 'success'
case 'medium':
return 'warning'
case 'high':
return 'error'
case 'overload':
return 'error'
default:
return 'default'
}
}
async function fetchData() {
loading.value = true
try {
const res = await getServerStatus()
// res.data
data.value = res.data
}
finally {
loading.value = false
}
}
onMounted(fetchData)
</script>
<template>
<n-space vertical>
<n-card>
<n-flex align="center">
<n-h3 prefix="bar" class="m-0">
服务器状态
</n-h3>
<n-text depth="3">
查看当前服务器与进程运行情况
</n-text>
<n-button type="primary" ghost class="ml-auto" @click="fetchData">
<template #icon>
<icon-park-outline-refresh />
</template>
刷新
</n-button>
</n-flex>
</n-card>
<!-- 基本信息 -->
<n-card title="基础信息" size="small">
<n-descriptions :columns="3" label-placement="left" bordered>
<n-descriptions-item label="主机名">
{{ data?.hostname || '-' }}
</n-descriptions-item>
<n-descriptions-item label="平台">
{{ data?.os.platform || '-' }}
</n-descriptions-item>
<n-descriptions-item label="架构">
{{ data?.os.arch || '-' }}
</n-descriptions-item>
<n-descriptions-item label="系统版本">
{{ data?.os.release || '-' }}
</n-descriptions-item>
<n-descriptions-item label="内核类型">
{{ data?.os.type || '-' }}
</n-descriptions-item>
<n-descriptions-item label="内核版本">
{{ data?.os.kernelVersion || '-' }}
</n-descriptions-item>
<n-descriptions-item label="系统负载">
<n-tag :type="loadColor(data?.os.load)">
{{ data?.os.load ?? '-' }}
</n-tag>
</n-descriptions-item>
<n-descriptions-item label="系统运行时长">
{{ data?.os.uptime || '-' }}
</n-descriptions-item>
<n-descriptions-item label="主要 IPv4">
{{ data?.network.primaryIPv4 || '-' }}
</n-descriptions-item>
<n-descriptions-item label="网卡数量">
{{ data?.network.interfaceCount || '-' }}
</n-descriptions-item>
</n-descriptions>
</n-card>
<!-- CPU 信息 -->
<n-card title="CPU" size="small">
<n-descriptions :columns="3" label-placement="left" bordered>
<n-descriptions-item label="型号" :span="3">
{{ data?.cpu.model || '-' }}
</n-descriptions-item>
<n-descriptions-item label="核心数">
{{ data?.cpu.cores || '-' }}
</n-descriptions-item>
<n-descriptions-item label="主频">
{{ data?.cpu.speed || '-' }}
</n-descriptions-item>
<n-descriptions-item label="逻辑核心">
{{ data?.cpu.logicalCores ?? '-' }}
</n-descriptions-item>
<n-descriptions-item label="用户使用率">
{{ data?.cpu.userUsage }}%
</n-descriptions-item>
<n-descriptions-item label="系统使用率">
{{ data?.cpu.systemUsage }}%
</n-descriptions-item>
<n-descriptions-item label="空闲率">
{{ data?.cpu.idle }}%
</n-descriptions-item>
</n-descriptions>
</n-card>
<!-- 内存信息 -->
<n-card title="内存" size="small">
<n-descriptions :columns="3" label-placement="left" bordered>
<n-descriptions-item label="总内存">
{{ data?.memory.total || '-' }}
</n-descriptions-item>
<n-descriptions-item label="已用内存">
{{ data?.memory.used || '-' }}
</n-descriptions-item>
<n-descriptions-item label="空闲内存">
{{ data?.memory.free || '-' }}
</n-descriptions-item>
<n-descriptions-item label="内存占用" :span="3">
<n-progress
:percentage="data?.memory.usedPercent || 0"
indicator-placement="inside"
processing
/>
</n-descriptions-item>
</n-descriptions>
</n-card>
<!-- 网络信息 -->
<n-card v-if="data?.network" title="网络" size="small">
<div class="grid grid-cols-1 gap-2">
<div v-for="(iface, idx) in data?.network.interfaces || []" :key="idx" class="p-2 rounded border border-[var(--divider-color)]">
<div class="font-medium mb-1">
{{ iface.name }} <n-text depth="3">
({{ iface.internal ? '内网/回环' : '外网' }})
</n-text>
</div>
<div class="grid grid-cols-3 gap-2">
<span>MAC: {{ iface.mac || '-' }}</span>
<span>IPv4: {{ iface.ipv4?.length ? iface.ipv4.join(', ') : '-' }}</span>
<span>IPv6: {{ iface.ipv6?.length ? iface.ipv6.join(', ') : '-' }}</span>
</div>
</div>
</div>
</n-card>
<!-- 进程信息 -->
<n-card title="进程" size="small">
<n-descriptions :columns="3" label-placement="left" bordered>
<n-descriptions-item label="PID">
{{ data?.process.pid || '-' }}
</n-descriptions-item>
<n-descriptions-item label="Node 版本">
{{ data?.process.nodeVersion || '-' }}
</n-descriptions-item>
<n-descriptions-item label="运行时长">
{{ data?.process.uptime || '-' }}
</n-descriptions-item>
</n-descriptions>
</n-card>
</n-space>
</template>
<style scoped>
</style>