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 测试通过
83 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
} |