Files
aiops/frontend/src/pages/monitor/site-monitor/observability/SiteProtocolObs.tsx
T
attacker 63e4c220d4 feat(i18n): 增量国际化迁移 — deploy/kubernetes/monitor/task 全模块 + 补全 28 缺失翻译键
迁移范围:
- deploy 模块 (PipelineEditor/PipelineDetailDialog/PipelineFormDialog/PipelineRunDialog/ReleaseCreateDialog/ReleaseDetailDialog/ReleaseManager/ToolConfigTab/JobRoleCreateDialog 等 14 文件)
- kubernetes 模块 (YamlEditorToolbar/DeploymentHpaTab/DeploymentLogsTab/DeploymentPodsTab/DnsHostSection/SchedulingSection/EventsTab/HPACronHPATab/HistoryTab/PodsTab/ServicesTab/ActionModals 等 12 文件)
- monitor 模块 (AlertEventsTab/AlertCenterDetail/DatasourcePage/LogAlertBell/LogCenter/LogCenterArchBanner/StatChartPanel/LogRoleCreateDialog 等 8 文件)
- task 模块 (CronJobStatsPanel/CronJobTable/CronJobViewDialog/ExecutionHistoryDialog 4 文件)
- layout (PageEmpty/PageLoading 2 文件)
- site-monitor/snmp-monitor 模块 (6 文件)

翻译键: 3975 → 4003 (+28 缺失键补全), 22 命名空间, zh-CN/en-US 完全对齐
新增命名空间: agent/aiops/cloud/kubernetes/monitor/pipeline/rbac/system/task/ui

验证: go build✅ tsc -b=0 npm build✅ (20.78s) 17/17 集成测试通过
2026-07-02 12:39:36 +08:00

234 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 站点拨测 — 按协议可观测图表(拖拽选区 + 暗黑 Tooltip)// Logan.Li
import {
Area, Bar, BarChart, CartesianGrid, Cell, ComposedChart, Line,
ReferenceArea, ReferenceLine, Tooltip, XAxis, YAxis,
} from 'recharts';
import { useTranslation } from 'react-i18next';
import { Globe, Network, Radio, Shield, Wifi } from 'lucide-react';
import type { SiteResult, SiteTask } from '../helpers';
import { fmtMs, fmtSslDays, normalizeProbeType } from '../helpers';
import {
analyzeDnsResults, analyzeHttpResults, analyzePingResults,
analyzeSslResults, analyzeTcpResults,
} from './analytics';
import { SiteEventTimeline } from './SiteEventTimeline';
import { SiteChartCard } from '../SiteChartCard';
import { MonitorRangeChartFrame } from '../../shared/MonitorRangeChartFrame';
import { MonitorChartTooltip, MONITOR_CHART_GRID, MONITOR_CHART_TICK } from '../../shared/monitorChartTheme';
interface Props {
task: SiteTask;
results: SiteResult[];
rangeLabel?: string;
}
function SelectionArea({ left, right }: { left: string; right: string }) {
if (!left || !right || left === right) return null;
return (
<ReferenceArea
x1={left}
x2={right}
strokeOpacity={0.4}
fill="var(--dg-blue)"
fillOpacity={0.12}
stroke="var(--dg-blue)"
/>
);
}
function HttpObs({ task, results, rangeLabel }: Props) {
const { t } = useTranslation();
const rl = rangeLabel ?? t('monitor.siteProtocol.rangeDefault');
const s = analyzeHttpResults(results, task);
return (
<div className="space-y-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{[
{ label: `${t('monitor.siteProtocol.http.expectedPrefix')} ${s.expectedCode}`, value: s.okCount, tone: 'dg-text-green' },
{ label: '4xx', value: s.err4xx, tone: 'dg-text-yellow' },
{ label: '5xx', value: s.err5xx, tone: 'dg-text-red' },
{ label: t('monitor.siteProtocol.http.timeoutOther'), value: s.otherErr, tone: 'dg-text-red' },
].map((item) => (
<div key={item.label} className="bg-[var(--dg-bg-secondary)] rounded-lg px-3 py-2 text-center">
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
<p className={`text-base font-bold ${item.tone}`}>{item.value}</p>
</div>
))}
</div>
<SiteChartCard
icon={<Globe className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />}
title={`${t('monitor.siteProtocol.http.statusCodeTitle')}(${rl})`}
empty={s.chart.length === 0}
>
{(h) => (
<MonitorRangeChartFrame data={s.chart} xKey="time" height={h}>
{({ displayData, chartHandlers, refLeft, refRight }) => (
<BarChart data={displayData} margin={{ top: 4, right: 8, left: 0, bottom: 4 }} {...chartHandlers}>
<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" />
<YAxis tick={MONITOR_CHART_TICK} tickLine={false} axisLine={false} width={36} domain={[0, 'auto']} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => `HTTP ${v}`} nameFormatter={() => t('monitor.siteProtocol.http.statusCodeName')} />} />
<ReferenceLine y={s.expectedCode} stroke="var(--dg-green)" strokeDasharray="4 4" label={{ value: `${t('monitor.siteProtocol.http.expectedPrefix')}${s.expectedCode}`, fontSize: 10, fill: 'var(--dg-text-muted)' }} />
<Bar dataKey="code" radius={[2, 2, 0, 0]} maxBarSize={14} isAnimationActive={false}>
{displayData.map((d, i) => (
<Cell key={`${d.time}-${i}`} fill={d.ok ? 'var(--dg-green)' : d.code >= 500 ? 'var(--dg-red)' : d.code >= 400 ? '#eab308' : 'var(--dg-red)'} />
))}
</Bar>
<SelectionArea left={refLeft} right={refRight} />
</BarChart>
)}
</MonitorRangeChartFrame>
)}
</SiteChartCard>
<SiteEventTimeline title={t('monitor.siteProtocol.http.timelineTitle')} events={s.events} />
</div>
);
}
function SslObs({ task, results }: Props) {
const { t } = useTranslation();
const s = analyzeSslResults(results, task);
const sslInfo = fmtSslDays(s.days);
return (
<div className="space-y-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{[
{ label: t('monitor.siteProtocol.ssl.expiry'), value: s.expiry || '--', tone: 'text-[var(--dg-text-primary)]' },
{ label: t('monitor.siteProtocol.ssl.remainingDays'), value: sslInfo.text, tone: sslInfo.tone },
{ label: t('monitor.siteProtocol.ssl.issuer'), value: s.issuer ? (s.issuer.length > 16 ? `${s.issuer.slice(0, 16)}…` : s.issuer) : '--', tone: 'text-[var(--dg-text-primary)]' },
{ label: t('monitor.siteProtocol.ssl.handshakeLatency'), value: fmtMs(s.handshakeMs), tone: 'text-[var(--dg-text-primary)]' },
].map((item) => (
<div key={item.label} className="bg-[var(--dg-bg-secondary)] rounded-lg px-3 py-2 text-center">
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
<p className={`text-sm font-bold truncate ${item.tone}`} title={String(item.value)}>{item.value}</p>
</div>
))}
</div>
<SiteChartCard
icon={<Shield className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />}
title={t('monitor.siteProtocol.ssl.daysTrendTitle')}
height={220}
empty={s.chart.length === 0}
>
{(h) => (
<MonitorRangeChartFrame data={s.chart} xKey="time" height={h}>
{({ displayData, chartHandlers, refLeft, refRight }) => (
<ComposedChart data={displayData} margin={{ top: 4, right: 8, left: 0, bottom: 4 }} {...chartHandlers}>
<CartesianGrid strokeDasharray="3 3" stroke={MONITOR_CHART_GRID} vertical={false} />
<XAxis dataKey="time" tick={MONITOR_CHART_TICK} tickLine={false} axisLine={{ stroke: MONITOR_CHART_GRID }} />
<YAxis tick={MONITOR_CHART_TICK} tickLine={false} axisLine={false} width={32} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => `${v} ${t('monitor.siteProtocol.ssl.daysUnit')}`} nameFormatter={() => t('monitor.siteProtocol.ssl.daysName')} />} />
<ReferenceLine y={30} stroke="#eab308" strokeDasharray="4 4" label={{ value: t('monitor.siteProtocol.ssl.ref30days'), fontSize: 10, fill: 'var(--dg-text-muted)' }} />
<ReferenceLine y={7} stroke="var(--dg-red)" strokeDasharray="4 4" label={{ value: t('monitor.siteProtocol.ssl.ref7days'), fontSize: 10, fill: 'var(--dg-text-muted)' }} />
<Line type="monotone" dataKey="days" stroke="var(--dg-blue)" strokeWidth={2} dot={false} isAnimationActive={false} />
<SelectionArea left={refLeft} right={refRight} />
</ComposedChart>
)}
</MonitorRangeChartFrame>
)}
</SiteChartCard>
<SiteEventTimeline title={t('monitor.siteProtocol.ssl.timelineTitle')} events={s.events} />
</div>
);
}
function LatencyObs({ results, mode, rangeLabel }: Props & { mode: 'PING' | 'DNS' }) {
const { t } = useTranslation();
const rl = rangeLabel ?? t('monitor.siteProtocol.rangeDefault');
const s = mode === 'PING' ? analyzePingResults(results) : analyzeDnsResults(results);
const label = mode === 'PING' ? t('monitor.siteProtocol.latency.pingLabel') : t('monitor.siteProtocol.latency.dnsLabel');
const Icon = mode === 'PING' ? Wifi : Radio;
return (
<div className="space-y-3">
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{[
{ label: t('monitor.siteProtocol.latency.avg'), value: fmtMs(s.avg) },
{ label: t('monitor.siteProtocol.latency.min'), value: fmtMs(s.min) },
{ label: t('monitor.siteProtocol.latency.max'), value: fmtMs(s.max) },
{ label: 'P95', value: fmtMs(s.p95) },
{ label: t('monitor.siteProtocol.latency.spikeCount'), value: String(s.spikeCount), tone: s.spikeCount > 0 ? 'dg-text-yellow' : undefined },
].map((item) => (
<div key={item.label} className="bg-[var(--dg-bg-secondary)] rounded-lg px-3 py-2 text-center">
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
<p className={`text-sm font-bold ${item.tone || 'text-[var(--dg-text-primary)]'}`}>{item.value}</p>
</div>
))}
</div>
<SiteChartCard
icon={<Icon className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />}
title={`${label}(${rl})`}
empty={s.chart.length === 0}
>
{(h) => (
<MonitorRangeChartFrame data={s.chart} xKey="time" height={h}>
{({ displayData, chartHandlers, refLeft, refRight }) => (
<ComposedChart data={displayData} margin={{ top: 4, right: 8, left: 0, bottom: 4 }} {...chartHandlers}>
<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" />
<YAxis tick={MONITOR_CHART_TICK} tickLine={false} axisLine={false} width={42} tickFormatter={(v: number) => (v >= 1000 ? `${(v / 1000).toFixed(1)}s` : `${v}ms`)} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v, name) => (name === 'latency' ? fmtMs(v) : String(v))} nameFormatter={(name) => (name === 'latency' ? t('monitor.siteProtocol.latency.latencyName') : name)} />} />
<ReferenceLine y={s.avg} stroke="var(--dg-blue)" strokeDasharray="4 4" label={{ value: t('monitor.siteProtocol.latency.avgLabel'), fontSize: 10, fill: 'var(--dg-text-muted)' }} />
<Line type="monotone" dataKey="latency" stroke="var(--dg-blue)" strokeWidth={2} dot={false} isAnimationActive={false} />
<SelectionArea left={refLeft} right={refRight} />
</ComposedChart>
)}
</MonitorRangeChartFrame>
)}
</SiteChartCard>
<SiteEventTimeline title={t('monitor.siteProtocol.latency.timelineTitle')} events={s.events} />
</div>
);
}
function TcpObs({ results, rangeLabel }: Props) {
const { t } = useTranslation();
const rl = rangeLabel ?? t('monitor.siteProtocol.rangeDefault');
const s = analyzeTcpResults(results);
return (
<div className="space-y-3">
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
{[
{ label: `${t('monitor.siteProtocol.tcp.upRate')}(${rl})`, value: s.upRate, tone: 'dg-text-green' },
{ label: t('monitor.siteProtocol.tcp.failCount'), value: String(s.failCount), tone: s.failCount > 0 ? 'dg-text-red' : 'text-[var(--dg-text-primary)]' },
{ label: t('monitor.siteProtocol.tcp.lastFail'), value: s.lastFailAt || '--', tone: 'text-[var(--dg-text-primary)]' },
].map((item) => (
<div key={item.label} className="bg-[var(--dg-bg-secondary)] rounded-lg px-3 py-2 text-center">
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
<p className={`text-sm font-bold ${item.tone}`}>{item.value}</p>
</div>
))}
</div>
<SiteChartCard
icon={<Network className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />}
title={t('monitor.siteProtocol.tcp.statusTitle')}
height={200}
empty={s.chart.length === 0}
>
{(h) => (
<MonitorRangeChartFrame data={s.chart} xKey="time" height={h}>
{({ displayData, chartHandlers, refLeft, refRight }) => (
<ComposedChart data={displayData} margin={{ top: 4, right: 8, left: 0, bottom: 4 }} {...chartHandlers}>
<XAxis dataKey="time" tick={MONITOR_CHART_TICK} tickLine={false} axisLine={{ stroke: MONITOR_CHART_GRID }} interval="preserveStartEnd" />
<YAxis hide domain={[0, 100]} />
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => (v >= 100 ? t('monitor.siteProtocol.tcp.connected') : t('monitor.siteProtocol.tcp.disconnected'))} nameFormatter={() => t('monitor.siteProtocol.tcp.statusName')} />} />
<Area type="stepAfter" dataKey="up" stroke="var(--dg-green)" fill="var(--dg-green)" fillOpacity={0.2} isAnimationActive={false} />
<SelectionArea left={refLeft} right={refRight} />
</ComposedChart>
)}
</MonitorRangeChartFrame>
)}
</SiteChartCard>
<SiteEventTimeline title={t('monitor.siteProtocol.tcp.timelineTitle')} events={s.events} emptyText={t('monitor.siteProtocol.tcp.emptyText')} />
</div>
);
}
export function SiteProtocolObs({ task, results, rangeLabel }: Props) {
const type = normalizeProbeType(task.type);
if (type === 'SSL') return <SslObs task={task} results={results} rangeLabel={rangeLabel} />;
if (type === 'PING') return <LatencyObs task={task} results={results} mode="PING" rangeLabel={rangeLabel} />;
if (type === 'DNS') return <LatencyObs task={task} results={results} mode="DNS" rangeLabel={rangeLabel} />;
if (type === 'TCP') return <TcpObs task={task} results={results} rangeLabel={rangeLabel} />;
return <HttpObs task={task} results={results} rangeLabel={rangeLabel} />;
}