feat: friendly process names + disks + public IPs + domains with copy + refresh btn
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
This commit is contained in:
parent
26f5750046
commit
ed1e78c5c2
@ -33,7 +33,7 @@ export default function Dashboard() {
|
|||||||
const { data: serverData } = useQuery({
|
const { data: serverData } = useQuery({
|
||||||
queryKey: ['servers', 'metrics'],
|
queryKey: ['servers', 'metrics'],
|
||||||
queryFn: getServerMetrics,
|
queryFn: getServerMetrics,
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 15_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const userTrendOption = useMemo(() => ({
|
const userTrendOption = useMemo(() => ({
|
||||||
@ -68,15 +68,18 @@ export default function Dashboard() {
|
|||||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||||
{serverData?.servers?.map(s => (
|
{serverData?.servers?.map(s => (
|
||||||
<Col xs={24} sm={12} lg={12} key={s.hostname}>
|
<Col xs={24} sm={12} lg={12} key={s.hostname}>
|
||||||
<div style={{ background: '#fff', borderRadius: 8, padding: '12px 16px', border: '1px solid #f0f0f0' }}>
|
<div style={{ background: '#fff', borderRadius: 8, padding: '10px 14px', border: '1px solid #f0f0f0' }}>
|
||||||
<Space style={{ marginBottom: 8 }}><ClusterOutlined style={{ color: '#1677ff' }} /><DText strong>{s.name}</DText><DText type="secondary" style={{ fontSize: 12 }}>{s.network.ip}</DText></Space>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
|
||||||
<Row gutter={12}>
|
<Space size={4}><ClusterOutlined style={{ color: '#1677ff' }} /><DText strong style={{ fontSize: 13 }}>{s.name}</DText></Space>
|
||||||
|
<DText type="secondary" style={{ fontSize: 11 }}>{s.network.privateIp} / {s.network.publicIp}</DText>
|
||||||
|
</div>
|
||||||
|
<Row gutter={8}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<DText type="secondary" style={{ fontSize: 11 }}>CPU {s.cpu.usagePercent}%</DText>
|
<DText type="secondary" style={{ fontSize: 11 }}>CPU {s.cpu.usagePercent}% ({s.cpu.cores}c)</DText>
|
||||||
<Progress percent={s.cpu.usagePercent} size="small" strokeColor={s.cpu.usagePercent > 80 ? '#ff4d4f' : '#1677ff'} showInfo={false} />
|
<Progress percent={s.cpu.usagePercent} size="small" strokeColor={s.cpu.usagePercent > 80 ? '#ff4d4f' : '#1677ff'} showInfo={false} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<DText type="secondary" style={{ fontSize: 11 }}>内存 {s.memory.percent}%</DText>
|
<DText type="secondary" style={{ fontSize: 11 }}>RAM {s.memory.percent}%</DText>
|
||||||
<Progress percent={s.memory.percent} size="small" strokeColor={s.memory.percent > 80 ? '#ff4d4f' : '#52c41a'} showInfo={false} />
|
<Progress percent={s.memory.percent} size="small" strokeColor={s.memory.percent > 80 ? '#ff4d4f' : '#52c41a'} showInfo={false} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@ -1,68 +1,110 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useState } from 'react'
|
||||||
import { Card, Row, Col, Progress, Table, Tag, Typography, theme, Space } from 'antd'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { CloudServerOutlined, DashboardOutlined } from '@ant-design/icons'
|
import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, message } from 'antd'
|
||||||
import { getServerMetrics, type ServerInfo } from '@/services/server-api'
|
import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons'
|
||||||
|
import { getServerMetrics, type ServerInfo, type ProcessInfo } from '@/services/server-api'
|
||||||
|
|
||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
|
|
||||||
function ServerCard({ server }: { server: ServerInfo }) {
|
function ServerCard({ server }: { server: ServerInfo }) {
|
||||||
theme.useToken()
|
const cpuColor = server.cpu.usagePercent > 80 ? '#ff4d4f' : server.cpu.usagePercent > 50 ? '#faad14' : '#52c41a'
|
||||||
const cpuColor = server.cpu.usagePercent > 80 ? 'red' : server.cpu.usagePercent > 50 ? 'orange' : 'green'
|
const memColor = server.memory.percent > 80 ? '#ff4d4f' : server.memory.percent > 50 ? '#faad14' : '#52c41a'
|
||||||
const memColor = server.memory.percent > 80 ? 'red' : server.memory.percent > 50 ? 'orange' : 'green'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title={<Space><CloudServerOutlined />{server.name}<Tag color="blue">{server.role}</Tag></Space>}
|
<Card
|
||||||
extra={<Text type="secondary">{server.network.ip}</Text>}
|
title={<Space><CloudServerOutlined />{server.name}<Tag color="blue">{server.role}</Tag></Space>}
|
||||||
style={{ height: '100%' }}>
|
style={{ height: '100%' }}
|
||||||
<Row gutter={[16, 16]}>
|
>
|
||||||
<Col span={12}>
|
{/* Network info */}
|
||||||
<Text type="secondary">CPU ({server.cpu.cores}核)</Text>
|
<Row gutter={[12, 8]} style={{ marginBottom: 16 }}>
|
||||||
<Progress percent={server.cpu.usagePercent} strokeColor={cpuColor} size="small" />
|
<Col span={24}>
|
||||||
<Text style={{ fontSize: 11 }} type="secondary">{server.cpu.model?.slice(0, 40)}</Text>
|
<Space wrap size="small">
|
||||||
</Col>
|
<Tag color="default" style={{ cursor: 'pointer' }} onClick={() => { navigator.clipboard.writeText(server.network.publicIp); message.success('已复制') }}>
|
||||||
<Col span={12}>
|
🌐 {server.network.publicIp} <CopyOutlined />
|
||||||
<Text type="secondary">内存</Text>
|
</Tag>
|
||||||
<Progress percent={server.memory.percent} strokeColor={memColor} size="small" />
|
<Tag color="default" style={{ cursor: 'pointer' }} onClick={() => { navigator.clipboard.writeText(server.network.privateIp); message.success('已复制') }}>
|
||||||
<Text style={{ fontSize: 11 }} type="secondary">{server.memory.used}/{server.memory.total}</Text>
|
🔒 {server.network.privateIp} <CopyOutlined />
|
||||||
</Col>
|
</Tag>
|
||||||
<Col span={12}>
|
{server.network.domains.map(d => (
|
||||||
<Text type="secondary">磁盘</Text>
|
<Tag key={d} color="blue" style={{ cursor: 'pointer' }} onClick={() => { navigator.clipboard.writeText(d); message.success('已复制') }}>
|
||||||
<Progress percent={server.disk.percent} strokeColor={server.disk.percent > 80 ? 'red' : 'blue'} size="small" />
|
<GlobalOutlined /> {d} <CopyOutlined />
|
||||||
<Text style={{ fontSize: 11 }} type="secondary">{server.disk.used}/{server.disk.total}</Text>
|
</Tag>
|
||||||
</Col>
|
))}
|
||||||
<Col span={12}>
|
</Space>
|
||||||
<Text type="secondary">运行时间</Text>
|
|
||||||
<div><Text strong>{server.uptime}</Text></div>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Metrics */}
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={12} sm={6}>
|
||||||
|
<Text type="secondary" style={{ fontSize: 11 }}>CPU ({server.cpu.cores}核)</Text>
|
||||||
|
<Progress percent={server.cpu.usagePercent} strokeColor={cpuColor} size="small" format={p => `${p}%`} />
|
||||||
|
<Text style={{ fontSize: 10 }} type="secondary">{server.cpu.model?.slice(0, 30)}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} sm={6}>
|
||||||
|
<Text type="secondary" style={{ fontSize: 11 }}>内存</Text>
|
||||||
|
<Progress percent={server.memory.percent} strokeColor={memColor} size="small" format={p => `${p}%`} />
|
||||||
|
<Text style={{ fontSize: 10 }} type="secondary">{server.memory.used}/{server.memory.total}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12}>
|
||||||
|
<Text type="secondary" style={{ fontSize: 11 }}>磁盘</Text>
|
||||||
|
{server.disks.map(d => (
|
||||||
|
<div key={d.mount} style={{ marginBottom: 4 }}>
|
||||||
|
<Space size={4}>
|
||||||
|
<Text style={{ fontSize: 10 }} type="secondary">{d.mount}</Text>
|
||||||
|
<Progress percent={d.percent} size="small" strokeColor={d.percent > 80 ? '#ff4d4f' : '#1677ff'} style={{ flex: 1, margin: 0 }} />
|
||||||
|
<Text style={{ fontSize: 10 }}>{d.used}/{d.total}</Text>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text type="secondary" style={{ fontSize: 11 }}>运行时间</Text>
|
||||||
|
<div><Text>{server.uptime}</Text></div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* Processes */}
|
||||||
<Table
|
<Table
|
||||||
dataSource={server.processes}
|
dataSource={server.processes}
|
||||||
rowKey="pid"
|
rowKey="pid" size="small" pagination={false}
|
||||||
size="small"
|
style={{ marginTop: 12 }}
|
||||||
pagination={false}
|
|
||||||
style={{ marginTop: 16 }}
|
|
||||||
columns={[
|
columns={[
|
||||||
{ title: 'PID', dataIndex: 'pid', width: 70 },
|
{ title: 'PID', dataIndex: 'pid', width: 60 },
|
||||||
{ title: 'CPU', dataIndex: 'cpu', width: 60 },
|
{ title: '进程', dataIndex: 'name', ellipsis: true, render: (name: string, r: ProcessInfo) => (
|
||||||
{ title: 'MEM', dataIndex: 'mem', width: 60 },
|
<Tooltip title={r.command}>{name}</Tooltip>
|
||||||
{ title: '命令', dataIndex: 'command', ellipsis: true },
|
)},
|
||||||
|
{ title: 'CPU', dataIndex: 'cpu', width: 55 },
|
||||||
|
{ title: 'MEM', dataIndex: 'mem', width: 55 },
|
||||||
]}
|
]}
|
||||||
locale={{ emptyText: '暂无进程数据' }}
|
locale={{ emptyText: '暂无进程' }}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ServersPage() {
|
export default function ServersPage() {
|
||||||
|
const qc = useQueryClient()
|
||||||
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ['servers', 'metrics'],
|
queryKey: ['servers', 'metrics'],
|
||||||
queryFn: getServerMetrics,
|
queryFn: getServerMetrics,
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 15_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
setRefreshing(true)
|
||||||
|
await qc.invalidateQueries({ queryKey: ['servers', 'metrics'] })
|
||||||
|
setTimeout(() => setRefreshing(false), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Title level={5} style={{ marginTop: 0 }}><DashboardOutlined /> 服务器运维</Title>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<Title level={5} style={{ margin: 0 }}><CloudServerOutlined /> 服务器运维</Title>
|
||||||
|
<Button icon={<ReloadOutlined spin={refreshing} />} onClick={handleRefresh} loading={refreshing}>刷新</Button>
|
||||||
|
</div>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{(data?.servers || []).map(s => (
|
{(data?.servers || []).map(s => (
|
||||||
<Col xs={24} lg={12} key={s.hostname}>
|
<Col xs={24} lg={12} key={s.hostname}>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import { api } from './http-client'
|
import { api } from './http-client'
|
||||||
|
|
||||||
|
export interface DiskInfo { mount: string; total: string; used: string; free: string; percent: number }
|
||||||
|
export interface ProcessInfo { pid: number; cpu: string; mem: string; name: string; command: string }
|
||||||
export interface ServerInfo {
|
export interface ServerInfo {
|
||||||
name: string
|
name: string; role: string; hostname: string;
|
||||||
role: string
|
cpu: { model: string; cores: number; usagePercent: number };
|
||||||
hostname: string
|
memory: { total: string; used: string; free: string; percent: number };
|
||||||
cpu: { model: string; cores: number; usagePercent: number; loadAvg: number[] }
|
disks: DiskInfo[];
|
||||||
memory: { total: string; used: string; free: string; percent: number }
|
uptime: string;
|
||||||
disk: { total: string; used: string; free: string; percent: number }
|
processes: ProcessInfo[];
|
||||||
uptime: string
|
network: { publicIp: string; privateIp: string; domains: string[] };
|
||||||
network: { ip: string }
|
|
||||||
processes: { pid: number; cpu: string; mem: string; command: string }[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServerMetrics(): Promise<{ servers: ServerInfo[] }> {
|
export function getServerMetrics(): Promise<{ servers: ServerInfo[] }> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user