Files
aiops/frontend/src/pages/monitor/snmp-monitor/SnmpOverviewCharts.tsx
T
attacker ecec73989b feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())
批量转换 163 个文件,3204 处中文替换为 t() 调用:
- Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换
- Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换
- Deploy: 流水线/发布/凭证/RBAC 全部转换
- Server/Cloud/Agent/Task/Layout/UI: 全部转换
- 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys)
- tsc 编译零错误
2026-07-03 01:51:57 +08:00

137 lines
5.6 KiB
TypeScript

// SNMP 概览图表 — 设备状态分布 + CPU 对比 // Logan.Li
import { useMemo } from 'react';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, Cell, PieChart, Pie,
} from 'recharts';
import { Card, CardContent } from '@/components/ui/card';
import { BarChart3, PieChart as PieChartIcon } from 'lucide-react';
import type { SnmpDevice, SnmpOverviewItem } from './helpers';
import { fmtMetricValue, statusColor } from './helpers';
import { MonitorChartTooltip, MONITOR_PIE_STROKE, MonitorPieTooltip } from '../shared/monitorChartTheme';
import { useTranslation } from 'react-i18next';
interface Props {
devices: SnmpDevice[];
overview: SnmpOverviewItem[];
}
export function SnmpOverviewCharts({ devices, overview }: Props) {
const { t } = useTranslation();
const statusData = useMemo(() => {
const online = devices.filter((d) => d.status === 'online').length;
const error = devices.filter((d) => d.status === 'error').length;
const offline = devices.length - online - error;
return [
{ name: t('common.online'), value: online, fill: 'var(--dg-green)' },
{ name: t('common.abnormal'), value: error, fill: 'var(--dg-red)' },
{ name: t('common.offline'), value: offline, fill: 'var(--dg-text-muted)' },
].filter((d) => d.value > 0);
}, [devices]);
const cpuData = useMemo(() => {
return overview
.filter((o) => o.cpu != null && o.cpu > 0)
.map((o) => ({
name: o.name.length > 10 ? `${o.name.slice(0, 10)}…` : o.name,
fullName: o.name,
cpu: o.cpu ?? 0,
fill: statusColor(o.status),
}))
.sort((a, b) => b.cpu - a.cpu);
}, [overview]);
if (devices.length === 0) return null;
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
<Card className="lg:col-span-2 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<BarChart3 className="h-4 w-4 text-[var(--dg-text-muted)]" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">{t('monitor.snmpoverviewcharts.s64f5a4')}</h3>
</div>
{cpuData.length === 0 ? (
<p className="text-xs text-[var(--dg-text-muted)] py-8 text-center">{t('monitor.snmpoverviewcharts.s1f4418')}</p>
) : (
<ResponsiveContainer width="100%" height={Math.max(160, cpuData.length * 36)}>
<BarChart data={cpuData} layout="vertical" margin={{ top: 0, right: 16, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--dg-border)" horizontal={false} />
<XAxis
type="number"
domain={[0, 100]}
tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }}
tickLine={false}
axisLine={{ stroke: 'var(--dg-border)' }}
tickFormatter={(v: number) => `${v}%`}
/>
<YAxis
type="category"
dataKey="name"
width={72}
tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }}
tickLine={false}
axisLine={false}
/>
<Tooltip
content={(
<MonitorChartTooltip
valueFormatter={(v) => fmtMetricValue(v, '%')}
nameFormatter={() => 'CPU'}
/>
)}
labelFormatter={(_, payload) => {
const item = payload?.[0]?.payload as { fullName?: string } | undefined;
return item?.fullName ?? '';
}}
/>
<Bar dataKey="cpu" radius={[0, 4, 4, 0]} maxBarSize={20} isAnimationActive={false}>
{cpuData.map((entry) => (
<Cell key={entry.fullName} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</CardContent>
</Card>
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<PieChartIcon className="h-4 w-4 text-[var(--dg-text-muted)]" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">{t('monitor.snmpoverviewcharts.sbb0ae9')}</h3>
</div>
<ResponsiveContainer width="100%" height={180}>
<PieChart>
<Pie
data={statusData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
innerRadius={42}
outerRadius={68}
paddingAngle={2}
isAnimationActive={false}
>
{statusData.map((entry) => (
<Cell key={entry.name} fill={entry.fill} stroke={MONITOR_PIE_STROKE} strokeWidth={2} />
))}
</Pie>
<Tooltip content={<MonitorPieTooltip />} />
</PieChart>
</ResponsiveContainer>
<div className="flex flex-wrap justify-center gap-3 mt-1">
{statusData.map((d) => (
<div key={d.name} className="flex items-center gap-1.5 text-xs text-[var(--dg-text-muted)]">
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: d.fill }} />
{d.name} {d.value}
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}