feat: 新增设备巡检模块实现完整的巡检告警功能

新增设备巡检模块核心功能,包括:
- 巡检数据存储与调度系统
- 华为/H3C交换机及Linux服务器采集器
- 设备扩展配置与告警规则管理
- 主机详情页巡检配置入口
- 产品名称自定义设置功能
- 网络设备SSH exec通道兼容处理
This commit is contained in:
2026-10-09 10:37:48 +08:00
parent 9202506c46
commit c64e00d835
75 changed files with 12087 additions and 446 deletions

View File

@@ -5,6 +5,9 @@ import {
HardDrive,
MemoryStick,
Network,
Server,
Plug,
Fan,
} from 'lucide-react';
import React, { memo, useEffect, useMemo, useState } from 'react';
import { useI18n } from '../../application/i18n/I18nProvider';
@@ -363,6 +366,8 @@ export const SystemOverviewTab = memo(function SystemOverviewTab({
isConnected: true,
});
const hasStats = Boolean(stats.lastUpdated);
// Network device detection: backend sets this field only for H3C/Huawei/Cisco etc.
const isNetworkDevice = Boolean(stats.networkDevicePorts);
const memoryPercent = ratioPercent(stats?.memUsed, stats?.memTotal);
const mountedDiskUsage = aggregateMountedDiskUsage(stats.disks);
@@ -374,6 +379,22 @@ export const SystemOverviewTab = memo(function SystemOverviewTab({
const loadOne = stats?.loadAverage?.[0] ?? null;
const loadPercent = ratioPercent(loadOne, stats?.cpuCores);
// Network device specific
const ndPorts = stats.networkDevicePorts ?? [];
const ndUp = stats.networkDevicePortUp ?? ndPorts.filter(p => p.phy === 'up').length;
const ndDown = stats.networkDevicePortDown ?? ndPorts.length - ndUp;
// Helper to shorten interface names: GigabitEthernet0/0/1 → GE0/0/1
const shortPortName = (name: string) => name
.replace(/^GigabitEthernet/i, 'GE')
.replace(/^TenGigEthernet/i, 'XGE')
.replace(/^TwentyFiveGigEthernet/i, '25GE')
.replace(/^FortyGigEthernet/i, '40GE')
.replace(/^HundredGigEthernet/i, '100GE')
.replace(/^Ethernet/i, 'Eth')
.replace(/^Serial/i, 'Ser')
.replace(/^Vlan-interface/i, 'Vlan');
useEffect(() => {
setHistory([]);
}, [sessionId]);
@@ -425,6 +446,89 @@ export const SystemOverviewTab = memo(function SystemOverviewTab({
<SystemPanelLoading message={t('systemManager.overview.loading')} />
) : showEmpty ? (
<SystemPanelEmpty icon={Activity} message={t('systemManager.overview.empty')} />
) : hasStats && isNetworkDevice ? (
/* ==================== NETWORK DEVICE VIEW ==================== */
<div className="flex-1 min-h-0 overflow-y-auto px-3 py-3 space-y-4">
{/* Banner */}
<div className="flex items-center gap-2 text-[11px] font-medium text-muted-foreground">
<Server size={13} className="text-violet-500" />
<span>网络设备模式 · VRP / Comware CLI</span>
</div>
{/* Main metric cards: CPU + Memory */}
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="CPU"
value={formatPercent(stats.cpu)}
detail="处理器使用率"
icon={Cpu}
gaugeValue={stats.cpu}
trendValues={trends.cpu}
trendMax={100}
tone="text-violet-500"
gradientId="nd-cpu-card"
/>
<MetricCard
label="内存"
value={memoryPercent !== null ? formatPercent(memoryPercent) : '--'}
detail={`${formatMemoryMb(stats.memUsed)} / ${formatMemoryMb(stats.memTotal)}`}
icon={MemoryStick}
gaugeValue={memoryPercent}
trendValues={trends.memory}
trendMax={100}
tone="text-emerald-500"
gradientId="nd-mem-card"
/>
</div>
{/* Port status summary */}
<section
className="rounded-lg border p-3"
style={{ borderColor: 'hsl(var(--border) / 0.7)', background: 'hsl(var(--background))' }}
>
<div className="mb-3 flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5 text-xs font-semibold" style={{ color: 'hsl(var(--foreground))' }}>
<Network size={13} className="text-cyan-500" />
端口状态
</div>
<div className="flex items-center gap-3 text-[11px]">
<span className="text-emerald-500">↑ {ndUp} UP</span>
<span className="text-muted-foreground">↓ {ndDown} DOWN</span>
<span className="text-muted-foreground">共 {ndPorts.length}</span>
</div>
</div>
{ndPorts.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{ndPorts.slice(0, 48).map((port) => (
<div
key={port.name}
className={cn(
'inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] font-mono tabular-nums',
port.phy === 'up'
? 'border-emerald-500/40 bg-emerald-500/10 text-emerald-600'
: 'border-muted/40 bg-muted/20 text-muted-foreground',
)}
title={`${port.name} — ${port.phy.toUpperCase()}`}
>
<span className={cn('h-1.5 w-1.5 rounded-full', port.phy === 'up' ? 'bg-emerald-500' : 'bg-muted-foreground/40')} />
{shortPortName(port.name)}
</div>
))}
{ndPorts.length > 48 && (
<span className="text-[10px] text-muted-foreground py-0.5">+{ndPorts.length - 48} 更多</span>
)}
</div>
) : (
<div className="text-[11px]" style={{ color: 'hsl(var(--muted-foreground))' }}>暂无端口数据</div>
)}
</section>
{/* Power / Fan status (placeholders — extend when we add display device parsing) */}
<div className="grid grid-cols-2 gap-3">
<InfoPill label="电源" value="未采集" icon={Plug} tone="text-amber-500" />
<InfoPill label="风扇" value="未采集" icon={Fan} tone="text-cyan-500" />
</div>
</div>
) : hasStats ? (
<div className="flex-1 min-h-0 overflow-y-auto px-3 py-3 space-y-4">
{/* Main metric cards */}