Files
aiops/frontend/src/pages/monitor/middleware-monitor/MwTrendChart.tsx
T
attacker a7838aaf65 refactor(i18n): U41 残余硬编码中文清零 — 工具函数+审计RDP标签+AIOps分析+Monitor残余+审批选项
18 文件 ~35 处 UI 硬编码中文→t()/i18n.t() 调用:
- agent-utils.ts timeAgo() 秒前/分钟前/小时前
- utils.ts formatDistanceToNow() 分钟前/小时前/天前
- request.ts 响应解析失败错误消息
- ActiveSessionsPanel/HistorySessionsPanel RDP 在线/历史会话标签
- MetricsExplorer 清除/已选/最近1小时
- HostMonitor 当前主机 subtitle
- AIAnalysisResult 证据链 (新增 useTranslation hook)
- capacity-predict.ts 容量预测摘要模板
- ApprovalResourcePage 阿里云/腾讯云/华为云
- ApprovalPermissionPage 堡垒机
- site-monitor/helpers.ts 频率/时间/证书天数格式化
- site-monitor/analytics.ts 证书剩余/检查失败/延迟波动事件
- middleware-monitor MwTargetChartsPanel/MwTrendChart/helpers.ts
- snmp-monitor SnmpMetricTrendChart/SnmpDeviceChartsPanel
- host-monitor/utils.ts 图表点数标签 + PortPolicyDialog 系统进程说明

新增 34 翻译键 (common.duration.secAgo/hourAgo/dayAgo + common.selected/
responseParseError/rdpActiveSessions/rdpHistorySessions/rdpSessionCount/
evidenceChain/last1Hour/currentHost + monitor.lbl 10键 + monitor.siteMonitor
8键 + approval.provider 3键 + approval.permType 1键 +
monitor.portPolicyDialog.systemProcessDesc + aiops.capacity.summary)

zh/en 7813→7847 对齐; tsc -b=0, npm build✓ (17.31s), ESLint=0/43/any=0,
go build=0, 17/17 测试通过
2026-07-13 22:21:47 +08:00

83 lines
3.4 KiB
TypeScript

// 中间件指标趋势图 — 光标拖拽选区 // Logan.Li
import { useMemo } from 'react';
import i18n from '@/i18n';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
ReferenceArea,
} from 'recharts';
import { fmtChartTime, fmtHitRate, fmtVal } from './helpers';
import { MonitorChartTooltip, MONITOR_CHART_GRID, MONITOR_CHART_TICK } from '../shared/monitorChartTheme';
import { useChartRangeZoom } from '../shared/useChartRangeZoom';
import { ChartRangeHint } from '../shared/ChartRangeHint';
interface Props {
data: Array<{ timestamp: number; value: number }>;
color: string;
unit?: string;
percent?: boolean;
formatter?: (v: number) => string;
height?: number;
showRangeHint?: boolean;
}
function fmtValue(v: number, percent?: boolean, formatter?: (v: number) => string, unit?: string): string {
if (formatter) return formatter(v);
if (percent) return fmtHitRate(v);
const base = fmtVal(v, unit ? 1 : 0);
return unit ? `${base} ${unit}` : base;
}
export function MwTrendChart({
data, color, unit, percent, formatter, height = 150, showRangeHint = true,
}: Props) {
const chartData = useMemo(
() => data.map((p) => ({ time: fmtChartTime(p.timestamp), value: p.value })),
[data],
);
const {
displayData, refLeft, refRight, selectionLabel, resetZoom, chartHandlers, zoomed,
} = useChartRangeZoom(chartData, 'time');
if (chartData.length === 0) {
return (
<div className="flex items-center justify-center text-xs text-[var(--dg-text-muted)]" style={{ height }}>
{i18n.t('monitor.lbl.noTrendData')}
</div>
);
}
const gradId = `mw-grad-${color.replace(/[^a-z]/gi, '')}`;
return (
<div>
<ResponsiveContainer width="100%" height={height}>
<AreaChart data={displayData} margin={{ top: 4, right: 6, left: 0, bottom: 4 }} {...chartHandlers}>
<defs>
<linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.28} />
<stop offset="100%" stopColor={color} stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke={MONITOR_CHART_GRID} vertical={false} />
<XAxis dataKey="time" tick={MONITOR_CHART_TICK} tickLine={false} axisLine={{ stroke: MONITOR_CHART_GRID }} interval="preserveStartEnd" minTickGap={32} />
<YAxis tick={MONITOR_CHART_TICK} tickLine={false} axisLine={false} width={42} domain={percent ? [0, 1] : ['auto', 'auto']} tickFormatter={(v: number) => (percent ? `${(v * 100).toFixed(0)}%` : fmtVal(v, 0))} />
<Tooltip
content={(
<MonitorChartTooltip
valueFormatter={(v) => fmtValue(v, percent, formatter, unit)}
/>
)}
/>
{refLeft && refRight && refLeft !== refRight && (
<ReferenceArea x1={refLeft} x2={refRight} strokeOpacity={0.4} fill="var(--dg-blue)" fillOpacity={0.12} />
)}
<Area type="monotone" dataKey="value" stroke={color} strokeWidth={2} fill={`url(#${gradId})`} dot={false} activeDot={{ r: 3, fill: color, stroke: 'var(--dg-bg-primary)', strokeWidth: 2 }} isAnimationActive={false} />
</AreaChart>
</ResponsiveContainer>
{showRangeHint && (
<ChartRangeHint label={selectionLabel} zoomed={zoomed} onReset={resetZoom} />
)}
</div>
);
}