Files
aiops/frontend/src/pages/monitor/middleware-monitor/MwOverviewCharts.tsx
T
attacker 55f216cfbb feat(i18n): 安全批量转换 135 文件 — 覆盖率 34%→62%
使用 i18n.t() 全局方式(避免 hook 定位问题):
- 288 目标文件中 135 个成功转换
- 2228 处中文替换为 i18n.t() 调用
- 1006 个新翻译 key 添加到 zh-CN.json + en-US.json
- 词典总量: 3554 keys (22 namespaces)
- tsc 零错误 + Vite 构建成功
- 6 个含代码示例/数据字符串的文件安全回退
2026-07-03 02:14:20 +08:00

142 lines
6.8 KiB
TypeScript

// 中间件概览图表 — 状态 + 命中率 + QPS/连接(支持放大)// Logan.Li
import { useMemo, type ReactNode } from 'react';
import {
BarChart, Bar, XAxis, YAxis, Tooltip,
ResponsiveContainer, Cell, PieChart, Pie,
} from 'recharts';
import { Card, CardContent } from '@/components/ui/card';
import { BarChart3, PieChart as PieChartIcon, Percent } from 'lucide-react';
import type { MwTarget } from './helpers';
import { fmtHitRate, fmtVal } from './helpers';
import { ChartExpandShell } from '../shared/ChartExpandShell';
import { MonitorChartTooltip, MONITOR_PIE_STROKE, MonitorPieTooltip } from '../shared/monitorChartTheme';
import i18n from '@/i18n';
interface Props {
targets: MwTarget[];
}
function shortName(name: string) {
return name.length > 10 ? `${name.slice(0, 10)}…` : name;
}
export function MwOverviewCharts({ targets }: Props) {
const statusData = useMemo(() => {
const online = targets.filter((t) => t.status === 'online').length;
const offline = targets.length - online;
return [
{ name: i18n.t('agent.agentOnline'), value: online, fill: 'var(--dg-green)' },
{ name: i18n.t('agent.agentOffline'), value: offline, fill: 'var(--dg-red)' },
].filter((d) => d.value > 0);
}, [targets]);
const redisHitData = useMemo(() => targets
.filter((t) => t.description === 'redis' && t.latest_metrics?.redis_hit_rate > 0)
.map((t) => ({
name: shortName(t.name), fullName: t.name,
value: (t.latest_metrics.redis_hit_rate || 0) * 100,
fill: (t.latest_metrics.redis_hit_rate || 0) >= 0.9 ? 'var(--dg-green)' : 'var(--dg-yellow)',
}))
.sort((a, b) => a.value - b.value), [targets]);
const redisQpsData = useMemo(() => targets
.filter((t) => t.description === 'redis' && t.latest_metrics?.redis_qps > 0)
.map((t) => ({ name: shortName(t.name), fullName: t.name, value: t.latest_metrics.redis_qps, fill: 'var(--dg-blue)' }))
.sort((a, b) => b.value - a.value), [targets]);
const mysqlConnData = useMemo(() => targets
.filter((t) => t.description === 'mysql' && t.latest_metrics?.mysql_connections > 0)
.map((t) => ({ name: shortName(t.name), fullName: t.name, value: t.latest_metrics.mysql_connections, fill: 'var(--dg-blue)' }))
.sort((a, b) => b.value - a.value), [targets]);
if (targets.length === 0) return null;
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
<OverviewChartCard title={i18n.t('monitor.mwoverviewchart.s2eeb27f9')} icon={<PieChartIcon className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={statusData.length === 0}>
{(h) => (
<ResponsiveContainer width="100%" height={h}>
<PieChart>
<Pie data={statusData} dataKey="value" nameKey="name" cx="50%" cy="50%"
innerRadius={h * 0.22} outerRadius={h * 0.34} paddingAngle={2} isAnimationActive={false}>
{statusData.map((e) => <Cell key={e.name} fill={e.fill} stroke={MONITOR_PIE_STROKE} strokeWidth={2} />)}
</Pie>
<Tooltip content={<MonitorPieTooltip />} />
</PieChart>
</ResponsiveContainer>
)}
</OverviewChartCard>
<OverviewChartCard title={i18n.t('monitor.mwoverviewchart.s29d98d86')} icon={<Percent className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={redisHitData.length === 0}>
{(h) => (
<ResponsiveContainer width="100%" height={h}>
<BarChart data={redisHitData} layout="vertical" margin={{ left: 0, right: 8 }}>
<XAxis type="number" domain={[0, 100]} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickFormatter={(v) => `${v}%`} />
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtHitRate(v / 100)} nameFormatter={() => i18n.t('monitor.mwtargetchartsp.s6787355b')} />} />
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
{redisHitData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</OverviewChartCard>
<OverviewChartCard title="Redis QPS" icon={<BarChart3 className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={redisQpsData.length === 0}>
{(h) => (
<ResponsiveContainer width="100%" height={h}>
<BarChart data={redisQpsData} layout="vertical" margin={{ left: 0, right: 8 }}>
<XAxis type="number" tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} />
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtVal(v, 1)} nameFormatter={() => 'QPS'} />} />
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
{redisQpsData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</OverviewChartCard>
<OverviewChartCard title={i18n.t('monitor.mwoverviewchart.s07d3e014')} icon={<BarChart3 className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={mysqlConnData.length === 0}>
{(h) => (
<ResponsiveContainer width="100%" height={h}>
<BarChart data={mysqlConnData} layout="vertical" margin={{ left: 0, right: 8 }}>
<XAxis type="number" tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} />
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtVal(v)} nameFormatter={() => i18n.t('monitor.mwoverviewchart.s30f7dd4e')} />} />
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
{mysqlConnData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</OverviewChartCard>
</div>
);
}
function OverviewChartCard({
title, icon, empty, children,
}: {
title: string;
icon: ReactNode;
empty?: boolean;
children: (height: number) => ReactNode;
}) {
return (
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-3">
<ChartExpandShell
title={title}
icon={icon}
className="bg-transparent p-0"
compactHeight={140}
expandedHeight={360}
empty={empty}
>
{children}
</ChartExpandShell>
</CardContent>
</Card>
);
}