// 主机监控详情 — 阿里云云监控风格 + v2.0 新增指标 // 进程TOP / Swap / FD / 上下文切换 // Logan.Li import { useState, useEffect, useMemo, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { agentApi } from '@/api'; import { useHostList, useHostHistory, usePortPolicy } from '@/hooks/api/useHostMonitor'; import { queryKeys } from '@/hooks/api/queryKeys'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { PageShell } from '@/components/layout/PageShell'; import { PageLoading } from '@/components/layout/PageLoading'; import { Monitor, HardDrive, Network, Server, Info, List, HeartPulse } from 'lucide-react'; import type { ZoomableChartHandle } from './host-monitor/ZoomableAreaChart'; import { DEFAULT_TIME_RANGE, PCT_REF_LINES, type AggType, type HostRecord, type HostMonitorTimeRange } from './host-monitor/types'; import { HostTimeRangePicker } from './host-monitor/HostTimeRangePicker'; import { getRangeSpanMinutes } from './host-monitor/timeRange'; import { parseJSON, buildMetricsFromHistory, getCurrentVal, filterHistoryByRange, getActualDataSpan } from './host-monitor/utils'; import { HealthProbeSection } from './host-monitor/HealthProbeSection'; import { PortPolicyDialog } from './host-monitor/PortPolicyDialog'; import { SystemInfoSection } from './host-monitor/SystemInfoSection'; import { HostCoreMetricsStrip } from './host-monitor/HostCoreMetricsStrip'; import { BasicTab } from './host-monitor/tabs/BasicTab'; import { DiskTab } from './host-monitor/tabs/DiskTab'; import { NetworkTab } from './host-monitor/tabs/NetworkTab'; import { PortMonitorTab } from './host-monitor/tabs/PortMonitorTab'; import { ProcessTab } from './host-monitor/tabs/ProcessTab'; import type { JsonData } from '@/types/common'; import i18n from '@/i18n'; const TABS = [ { id: 'basic', label: i18n.t('基础监控'), icon: }, { id: 'disk', label: i18n.t('磁盘监控'), icon: }, { id: 'network', label: i18n.t('网络监控'), icon: }, { id: 'ports', label: i18n.t('agent.portMonitor'), icon: }, { id: 'process', label: i18n.t('monitor.hostProcess'), icon: }, { id: 'system', label: i18n.t('system.systemInfo'), icon: }, { id: 'health_probes', label: i18n.t('kubernetes.container.probes'), icon: }, ]; export function HostMonitor({ embedded = false }: { embedded?: boolean }) { const qc = useQueryClient(); const [searchParams] = useSearchParams(); const urlHost = searchParams.get('host') || ''; const [selectedHost, setSelectedHost] = useState(urlHost); const [activeTab, setActiveTab] = useState('basic'); const [timeRange, setTimeRange] = useState(DEFAULT_TIME_RANGE); const [aggType, setAggType] = useState('avg'); const [portPolicyMode, setPortPolicyMode] = useState('whitelist'); const [portWhitelist, setPortWhitelist] = useState([]); const [portPolicyDialogOpen, setPortPolicyDialogOpen] = useState(false); const [portPolicySaving, setPortPolicySaving] = useState(false); const fdChartRef = useRef(null); const [fdChartZoomed, setFdChartZoomed] = useState(false); const { data: hosts = [], isLoading: hostsLoading } = useHostList(); const { data: rawHistory = [], isLoading: historyLoading } = useHostHistory(selectedHost, timeRange); const { data: portPolicy, refetch: refetchPortPolicy } = usePortPolicy(selectedHost); useEffect(() => { if (portPolicy) { setPortPolicyMode(portPolicy.port_filter_mode); setPortWhitelist(portPolicy.port_whitelist); } }, [portPolicy]); useEffect(() => { if (hosts.length > 0 && !selectedHost) { const m = urlHost ? hosts.find((h) => h.hostname === urlHost) : null; setSelectedHost(String(m?.hostname || hosts[0]?.hostname || '')); } }, [hosts, selectedHost, urlHost]); const handleSavePortPolicy = async () => { if (!selectedHost) return; setPortPolicySaving(true); try { await agentApi.savePortPolicy(selectedHost, { port_filter_mode: portPolicyMode, port_whitelist: portWhitelist, }); setPortPolicyDialogOpen(false); qc.invalidateQueries({ queryKey: queryKeys.hostMonitor.portPolicy(selectedHost) }); } catch { // 保存失败时保持弹窗状态,错误由调用方统一处理。// Logan.Li } finally { setPortPolicySaving(false); } }; const history = useMemo(() => filterHistoryByRange(rawHistory, timeRange), [rawHistory, timeRange]); const spanMinutes = getRangeSpanMinutes(timeRange); const timeConfig = useMemo(() => ({ buckets: timeRange.buckets, spanMinutes }), [timeRange.buckets, spanMinutes]); const actualDataSpan = useMemo(() => getActualDataSpan(history, timeRange), [history, timeRange]); const loading = hostsLoading || historyLoading; const serverInfo = (hosts.find((h) => h.hostname === selectedHost) || null) as HostRecord | null; const latest = useMemo((): HostRecord => { const base = serverInfo || {}; if (history.length === 0) return base; return { ...base, ...(history[history.length - 1] as HostRecord) }; }, [serverInfo, history]); const metrics = useMemo(() => buildMetricsFromHistory(history, timeConfig), [history, timeConfig]); const netInfo = parseJSON(latest?.net_info); const portInfo: JsonData[] = Array.isArray(latest?.port_info) ? latest.port_info as JsonData[] : (Array.isArray(parseJSON(latest?.port_info)) ? parseJSON(latest?.port_info) as unknown as JsonData[] : []); const diskIOInfo = parseJSON(latest?.disk_io_info); const swapInfo = parseJSON(latest?.swap_info); const processTop: JsonData[] = Array.isArray(parseJSON(latest?.process_top_info)) ? parseJSON(latest?.process_top_info) as unknown as JsonData[] : []; const fdInfo = parseJSON(latest?.fd_info); const csInfo = parseJSON(latest?.cs_info); const heartbeatInfo = parseJSON(latest?.heartbeat_info); const currentVal = (key: string, fallback = '--') => getCurrentVal(latest, netInfo, diskIOInfo, swapInfo, fdInfo, key, fallback); const toolbar = (
{serverInfo && ( {String(serverInfo.os || '')}{serverInfo.cpu_cores ? ` | ${serverInfo.cpu_cores as number | string} ${i18n.t('monitor.hostMonitorPanel.cores')}` : ''}{serverInfo.mem_total ? ` | ${(serverInfo.mem_total as number).toFixed(0)}GB` : ''} )}
{actualDataSpan && ( {i18n.t('monitor.hostMonitorPanel.dataSpan', { span: actualDataSpan })} )}
); const monitorBody = ( {TABS.map(tab => ( {tab.icon} {tab.label} ))} { refetchPortPolicy(); setPortPolicyDialogOpen(true); }} /> ); const dialogs = ( ); if (embedded) { return (
{toolbar} {!loading && history.length > 0 && ( )} {loading && !history.length ? : monitorBody} {dialogs}
); } return ( } toolbar={toolbar} loading={loading && !history.length} loadingLabel={i18n.t('monitor.hostmonitortsx.sc23e802e')} contentClassName="space-y-3"> {monitorBody} {dialogs} ); }