diff --git a/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx b/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx deleted file mode 100644 index fcce414..0000000 --- a/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx +++ /dev/null @@ -1,496 +0,0 @@ -// 主机监控大屏 — Grafana 风格:统计卡片 + 资源条状图 + 可排序列表 // Logan.Li -// 参照: Grafana node-dashboard.json (表格+时序) / kubernetes-dashboard.json (bargauge+阈值) -import { useState, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { - Server, - Cpu, - HardDrive, - Activity, - Monitor, - Bell, - ArrowUpDown, - ArrowUp, - ArrowDown, - RefreshCw, - Search, - ChevronLeft, - ChevronRight, - Wifi, - WifiOff, -} from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { - Select, SelectContent, SelectItem, SelectTrigger, SelectValue -} from '@/components/ui/select'; -import { serverApi } from '@/api'; -import type { JsonData } from '@/types/common'; - -type SortField = 'hostname' | 'ip' | 'agent_version' | 'agent_status' | 'cpu_usage' | 'memory_usage' | 'disk_usage' | 'region'; -type SortDir = 'asc' | 'desc'; - -function getUsageBar(val: number) { - const v = val || 0; - const color = v > 85 ? 'bg-red-500' : v > 60 ? 'bg-amber-500' : 'bg-emerald-500'; - return ( -
-
-
-
- {v.toFixed(0)}% -
- ); -} - -export default function HostMonitorDashboard() { - const { t } = useTranslation(['monitor', 'common']); - const navigate = useNavigate(); - const [hosts, setHosts] = useState([]); - const [total, setTotal] = useState(0); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - const [search, setSearch] = useState(''); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); - - // 排序状态 - const [sortField, setSortField] = useState('hostname'); - const [sortDir, setSortDir] = useState('asc'); - - // 统计 - const [stats, setStats] = useState<{ online: number; offline: number; total: number }>({ online: 0, offline: 0, total: 0 }); - - const loadData = useCallback(async (isRefresh = false) => { - if (isRefresh) setRefreshing(true); - else setLoading(true); - try { - const params: JsonData = { page, page_size: pageSize }; - if (search.trim()) params.keyword = search.trim(); - const res = await serverApi.getServers(params); - const list = (res as JsonData)?.list || []; - setHosts(list); - setTotal((res as JsonData)?.total || 0); - - // 统计 - const online = list.filter((h: JsonData) => h.agent_status === 'online').length; - setStats({ online, offline: list.length - online, total: (res as JsonData)?.total || 0 }); - } catch { - setHosts([]); - setTotal(0); - } finally { - setLoading(false); - setRefreshing(false); - } - }, [page, pageSize, search]); - - useEffect(() => { loadData(); }, [loadData]); - - // 排序后的数据 - const sorted = [...hosts].sort((a: JsonData, b: JsonData) => { - let va: JsonData | undefined, vb: JsonData | undefined; - if (sortField === 'region') { - va = (a.region?.name || a.region?.Name || '').toLowerCase(); - vb = (b.region?.name || b.region?.Name || '').toLowerCase(); - } else if (sortField === 'agent_status') { - va = a.agent_status || 'offline'; - vb = b.agent_status || 'offline'; - } else if (sortField === 'agent_version') { - va = a.agent_version || ''; - vb = b.agent_version || ''; - } else { - va = a[sortField] || ''; - vb = b[sortField] || ''; - } - - if (typeof va === 'number' && typeof vb === 'number') { - return sortDir === 'asc' ? va - vb : vb - va; - } - const sa = String(va).toLowerCase(); - const sb = String(vb).toLowerCase(); - if (sa < sb) return sortDir === 'asc' ? -1 : 1; - if (sa > sb) return sortDir === 'asc' ? 1 : -1; - return 0; - }); - - const handleSort = (field: SortField) => { - if (sortField === field) { - setSortDir(d => d === 'asc' ? 'desc' : 'asc'); - } else { - setSortField(field); - setSortDir('asc'); - } - }; - - const SortIcon = ({ field }: { field: SortField }) => { - if (sortField !== field) return ; - return sortDir === 'asc' - ? - : ; - }; - - const totalPages = Math.ceil(total / pageSize); - - const handleSearch = () => { - setPage(1); - loadData(); - }; - - // 状态标签颜色 - const statusBadge = (status: string) => { - if (status === 'online') return {t('common:online')}; - if (status === 'running') return {t('common:running')}; - if (status === 'stopped') return {t('common:stopped')}; - return {status || '--'}; - }; - - return ( -
- {/* 统计卡片 — Grafana Quick Facts 风格 (参照 JVM Dashboard) */} -
- - -
-
-
{stats.total}
-
{t('monitor:hostDashboard.hostTotal')}
-
-
-
- - -
-
-
{stats.online}
-
{t('monitor:hostDashboard.agentOnline')}
-
-
-
- - -
-
-
{stats.offline}
-
{t('common:offline')}
-
-
-
- {(() => { - const onlines = hosts.filter((h: JsonData) => h.agent_status === 'online'); - const avgCpu = onlines.length > 0 ? onlines.reduce((s: number, h: JsonData) => s + (h.cpu_usage || 0), 0) / onlines.length : 0; - const avgMem = onlines.length > 0 ? onlines.reduce((s: number, h: JsonData) => s + (h.memory_usage || 0), 0) / onlines.length : 0; - const cpuColor = avgCpu > 85 ? 'dg-text-red' : avgCpu > 60 ? 'dg-text-yellow' : 'dg-text-green'; - const memColor = avgMem > 85 ? 'dg-text-red' : avgMem > 60 ? 'dg-text-yellow' : 'dg-text-green'; - return (<> - - -
-
-
{avgCpu.toFixed(1)}%
-
{t('monitor:hostDashboard.avgCpu')}
-
-
-
- - -
-
-
{avgMem.toFixed(1)}%
-
{t('monitor:hostDashboard.avgMemory')}
-
-
-
- ); - })()} -
- - {/* 资源条状图 — Grafana Bargauge 风格 (参照 K8s Node CPU/Memory Ratio) */} - {hosts.filter((h: JsonData) => h.agent_status === 'online').length > 0 && ( -
- {/* CPU 排行 */} - - - - - {t('monitor:hostDashboard.cpuTop5')} - {t('monitor:hostDashboard.threshold')} - - - - {[...hosts] - .filter((h: JsonData) => h.agent_status === 'online') - .sort((a: JsonData, b: JsonData) => (b.cpu_usage || 0) - (a.cpu_usage || 0)) - .slice(0, 5) - .map((h: JsonData) => { - const val = h.cpu_usage || 0; - return ( -
- {h.hostname} -
-
= 85 - ? 'linear-gradient(90deg, #ef4444, #f87171)' - : val >= 60 - ? 'linear-gradient(90deg, #f59e0b, #fbbf24)' - : 'linear-gradient(90deg, #10b981, #34d399)' - }} - /> -
- = 85 ? 'dg-text-red' : val >= 60 ? 'dg-text-yellow' : 'dg-text-green' - }`}>{val.toFixed(1)}% -
- ); - })} - - - - {/* 内存排行 */} - - - - - {t('monitor:hostDashboard.memTop5')} - {t('monitor:hostDashboard.threshold')} - - - - {[...hosts] - .filter((h: JsonData) => h.agent_status === 'online') - .sort((a: JsonData, b: JsonData) => (b.memory_usage || 0) - (a.memory_usage || 0)) - .slice(0, 5) - .map((h: JsonData) => { - const val = h.memory_usage || 0; - return ( -
- {h.hostname} -
-
= 85 - ? 'linear-gradient(90deg, #ef4444, #f87171)' - : val >= 60 - ? 'linear-gradient(90deg, #f59e0b, #fbbf24)' - : 'linear-gradient(90deg, #10b981, #34d399)' - }} - /> -
- = 85 ? 'dg-text-red' : val >= 60 ? 'dg-text-yellow' : 'dg-text-green' - }`}>{val.toFixed(1)}% -
- ); - })} - - -
- )} - - {/* 表格卡片 */} - - -
-
- - {t('monitor:hostDashboard.listTitle')} - {t('monitor:hostDashboard.realtime')} -
-
-
- - setSearch(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - /> -
- -
-
-
- - {loading ? ( -
- -

{t('common:loading')}

-
- ) : sorted.length === 0 ? ( -
- -

{t('monitor:hostDashboard.noData')}

-

{t('monitor:hostDashboard.noDataDesc')}

-
- ) : ( - <> -
- - - - {[ - { key: 'hostname', label: t('monitor:hostDashboard.colHostId'), w: '' }, - { key: 'agent_version', label: t('monitor:hostDashboard.colAgentVersion'), w: 'w-[100px]' }, - { key: 'agent_status', label: t('monitor:hostDashboard.colAgentStatus'), w: 'w-[90px]' }, - { key: 'cpu_usage', label: 'CPU', w: 'w-[130px]' }, - { key: 'memory_usage', label: t('monitor:hostMetric.memory'), w: 'w-[130px]' }, - { key: 'disk_usage', label: t('monitor:hostMetric.disk'), w: 'w-[130px]' }, - { key: 'region', label: t('monitor:hostDashboard.colRegion'), w: 'w-[120px]' }, - { key: 'ip', label: t('monitor:hostDashboard.colIp'), w: 'w-[140px]' }, - ].map(col => ( - - ))} - - - - - {sorted.map((h: JsonData) => ( - - {/* 主机名/ID */} - - {/* Agent 版本 */} - - {/* Agent 状态 */} - - {/* CPU 使用率 */} - - {/* 内存使用率 */} - - {/* 磁盘使用率 */} - - {/* 所在区域 */} - - {/* IP 地址 */} - - {/* 操作栏 */} - - - ))} - -
handleSort(col.key as SortField)} - > -
- {col.label} - -
-
{t('common:actions')}
-
-
-
-
{h.hostname}
-
ID: {h.id}
-
-
-
- {h.agent_version || '--'} - - {statusBadge(h.agent_status)} - -
- - {getUsageBar(h.cpu_usage)} -
-
-
- - {getUsageBar(h.memory_usage)} -
-
-
- - {getUsageBar(h.disk_usage)} -
-
- - {h.region?.name || h.region?.Name || (h.region ? h.region : '--')} - - - {h.ip || '--'} - -
- - -
-
-
- - {/* 分页 */} -
-
- {t('monitor:hostDashboard.perPage')} - - {t('monitor:hostDashboard.totalCount', { count: total })} -
-
- - - {page} / {Math.max(totalPages, 1)} - - -
-
- - )} -
-
-
- ); -}