refactor(i18n): U40 Monitor + Agent 子组件残余硬编码中文 i18n 补全 — 14 文件 ~50 处→0
- ContextSwitchCard: CTX/IRQ/次 → t() - ProcessTable: agentWaitData/cpuUsagePercent → t() - FDUsageCard: fdHandles → t() - PortMonitorTab: 黑名单/白名单/策略配置/暂无端口数据 → t() - PortPolicyDialog: 白名单全量上报/黑名单仅上报/端口列表/逗号分隔/白名单无需配置/已输入端口 → t() - HostTimeRangePicker: 时间范围/自定义/取消/应用 → t() - DualLineChart: 接收/发送 → t(), i18n.t→useTranslation hook - ZoomableAreaChart: 暂无数据 → t() - SiteResultsPanel: 选择任务/次/可用率/立即拨测/SSL证书预警/到期/剩余/阈值/天 → t() - MiddlewareMonitor: Redis单机集群/上次更新/添加平台默认/暂无实例 → t() - SiteMonitor: subtitle → t() - SNMPMonitor: 标题/采集间隔/IP地址/OID模板/SNMP版本/确认/暂无设备/采集指标/暂无指标 → t() - HealthProbeSection: TCP目标地址/TCP端口 → t() - AgentAlerts: Agent告警/刷新/未恢复/已恢复/恢复/新建规则/创建第一条规则 → i18n.t() 新增 monitor.lbl 46 语义化翻译键 + agent.alerts 6 键 zh/en 7762→7814 对齐 tsc -b=0, npm build✓ (16.88s), ESLint=0/43/any=0, go build=0, 17/17 测试通过
This commit is contained in:
1 parent
70a7c2907c
commit
4e2ba77aa8
17 files changed
+165
-61
No files matched your search
@@ -113,12 +113,12 @@ export function AgentAlerts() {
|
||||
{/* 页头 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">Agent 告警</h2>
|
||||
<h2 className="text-xl font-semibold text-[var(--dg-text-primary)]">{i18n.t('agent.alerts.title')}</h2>
|
||||
<p className="text-sm dg-text-secondary mt-0.5"> {i18n.t('agent.alerts.thresholdTriggered')} </p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={loadData} disabled={loading} className="dg-input text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)]">
|
||||
<RefreshCw className={`h-4 w-4 mr-1.5 ${loading ? 'animate-spin' : ''}`} />
|
||||
刷新
|
||||
{i18n.t('common.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@ export function AgentAlerts() {
|
||||
onClick={() => setResolvedView(false)}
|
||||
className={!resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
|
||||
>
|
||||
未恢复
|
||||
{i18n.t('agent.alerts.unresolved')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -145,7 +145,7 @@ export function AgentAlerts() {
|
||||
onClick={() => setResolvedView(true)}
|
||||
className={resolvedView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'dg-input text-[var(--dg-text-secondary)]'}
|
||||
>
|
||||
已恢复
|
||||
{i18n.t('agent.alerts.resolved')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -207,7 +207,7 @@ export function AgentAlerts() {
|
||||
className="shrink-0 h-8 dg-input dg-text-green hover:bg-emerald-500/10 hover:dg-text-green"
|
||||
>
|
||||
<CheckCircle className="h-3.5 w-3.5 mr-1" />
|
||||
恢复
|
||||
{i18n.t('agent.alerts.resolve')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -223,7 +223,7 @@ export function AgentAlerts() {
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={openCreateRule} className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
新建规则
|
||||
{i18n.t('agent.alerts.newRule')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -241,7 +241,7 @@ export function AgentAlerts() {
|
||||
<p className="dg-text-secondary"> {i18n.t('monitor.noRules')} </p>
|
||||
<Button size="sm" onClick={openCreateRule} className="mt-4 bg-blue-600 hover:bg-blue-700 text-white">
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
创建第一条规则
|
||||
{i18n.t('agent.alerts.createFirstRule')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -4377,7 +4377,53 @@
|
||||
"noProbeHint": "No health probes. Click \"{{action}}\" to create one.",
|
||||
"searchFields": "Search fields",
|
||||
"receiveSend": "RX {{rx}} · TX {{tx}}",
|
||||
"currentFDUsage": "Current {{pct}}%"
|
||||
"currentFDUsage": "Current {{pct}}%",
|
||||
"ctxPerSec": "CTX switches/sec",
|
||||
"irqPerSec": "IRQ interrupts/sec",
|
||||
"times": "times",
|
||||
"agentWaitData": "Agent v2.0+ required, waiting for data…",
|
||||
"cpuUsagePercent": "CPU Usage(%)",
|
||||
"fdHandles": "FD File Handles",
|
||||
"blacklistLabel": "Blacklist",
|
||||
"whitelistModeLabel": "Whitelist mode",
|
||||
"policyConfig": "Policy Config",
|
||||
"noPortData": "No port data",
|
||||
"whitelistFullReport": "Whitelist — Report all",
|
||||
"blacklistSelectedOnly": "Blacklist — Report selected only",
|
||||
"portListLabel": "Port list",
|
||||
"portListHint": "(comma-separated, e.g.: 80,443,3306,6379)",
|
||||
"whitelistNoConfig": "— No config needed in whitelist mode",
|
||||
"portsEntered": "{{count}} ports entered",
|
||||
"timeRange": "Time range",
|
||||
"custom": "Custom",
|
||||
"apply": "Apply",
|
||||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"noData": "No data",
|
||||
"selectTaskHint": "Select a task to view protocol-specific metrics",
|
||||
"probeNow": "Probe now",
|
||||
"sslCertWarning": "SSL Certificate Warning",
|
||||
"sslExpiring": "Expires",
|
||||
"sslRemaining": "remaining",
|
||||
"sslThreshold": "threshold",
|
||||
"sslDays": "days",
|
||||
"availabilityRate": "Availability",
|
||||
"redisMysqlAutoRefresh": "Redis standalone/cluster · MySQL · 30s auto refresh",
|
||||
"lastUpdate": "Last update",
|
||||
"addPlatformDefault": "Add platform default",
|
||||
"noInstancePrefix": "No",
|
||||
"instanceSuffix": "instances",
|
||||
"siteMonitorSubtitle": "HTTP Web probe · SSL cert detection · TCP / DNS / PING",
|
||||
"snmpMonitorTitle": "SNMP Monitor",
|
||||
"snmpCollectInterval": "5 min collection · 7 day retention",
|
||||
"tcpTargetAddr": "TCP target address",
|
||||
"tcpPort": "TCP port",
|
||||
"ipAddress": "IP address",
|
||||
"snmpOidTemplate": "OID Template",
|
||||
"snmpVersion": "SNMP Version",
|
||||
"snmpNoDevice": "No SNMP devices. Click \"Add Device\" to start monitoring.",
|
||||
"snmpCollectMetrics": "Collected Metrics",
|
||||
"snmpNoMetrics": "No metrics yet. Please collect first."
|
||||
},
|
||||
"logAlertBell": {
|
||||
"linked": "Alert Policy Linked",
|
||||
@@ -7043,7 +7089,7 @@
|
||||
"loadFailed": "Failed to load alert data",
|
||||
"resolveAlert": "Resolve Alert",
|
||||
"resolveConfirm": "Mark this alert as resolved?",
|
||||
"resolved": "Alert resolved",
|
||||
"resolved": "Resolved",
|
||||
"deleteRule": "Delete Rule",
|
||||
"thresholdTriggered": "Alert threshold triggered",
|
||||
"noResolved": "No resolved alerts",
|
||||
@@ -7051,7 +7097,12 @@
|
||||
"metricSwap": "Swap Usage",
|
||||
"metricLoad1": "1-min Load Average",
|
||||
"metricInode": "Inode Usage",
|
||||
"metricTcpEst": "TCP Established"
|
||||
"metricTcpEst": "TCP Established",
|
||||
"title": "Agent Alerts",
|
||||
"unresolved": "Unresolved",
|
||||
"resolve": "Resolve",
|
||||
"newRule": "New Rule",
|
||||
"createFirstRule": "Create your first rule"
|
||||
},
|
||||
"allPortsHint": "All ports reported automatically",
|
||||
"allPortsMode": "All Ports",
|
||||
|
||||
@@ -4377,7 +4377,53 @@
|
||||
"noProbeHint": "暂无健康探针,点击「{{action}}」创建",
|
||||
"searchFields": "搜索字段",
|
||||
"receiveSend": "接收 {{rx}} · 发送 {{tx}}",
|
||||
"currentFDUsage": "当前 {{pct}}%"
|
||||
"currentFDUsage": "当前 {{pct}}%",
|
||||
"ctxPerSec": "CTX 切换/秒",
|
||||
"irqPerSec": "IRQ 中断/秒",
|
||||
"times": "次",
|
||||
"agentWaitData": "Agent v2.0+ 支持,等待数据上报…",
|
||||
"cpuUsagePercent": "CPU使用率(%)",
|
||||
"fdHandles": "FD 文件句柄",
|
||||
"blacklistLabel": "黑名单",
|
||||
"whitelistModeLabel": "白名单模式",
|
||||
"policyConfig": "策略配置",
|
||||
"noPortData": "暂无端口数据",
|
||||
"whitelistFullReport": "白名单 ─ 全量上报",
|
||||
"blacklistSelectedOnly": "黑名单 ─ 仅上报选中",
|
||||
"portListLabel": "端口列表",
|
||||
"portListHint": "(逗号分隔,如: 80,443,3306,6379)",
|
||||
"whitelistNoConfig": "— 白名单模式无需配置",
|
||||
"portsEntered": "已输入 {{count}} 个端口",
|
||||
"timeRange": "时间范围",
|
||||
"custom": "自定义",
|
||||
"apply": "应用",
|
||||
"rx": "接收",
|
||||
"tx": "发送",
|
||||
"noData": "暂无数据",
|
||||
"selectTaskHint": "选择任务查看协议专属可观测指标",
|
||||
"probeNow": "立即拨测",
|
||||
"sslCertWarning": "SSL 证书预警",
|
||||
"sslExpiring": "到期",
|
||||
"sslRemaining": "剩余",
|
||||
"sslThreshold": "阈值",
|
||||
"sslDays": "天",
|
||||
"availabilityRate": "可用率",
|
||||
"redisMysqlAutoRefresh": "Redis 单机/集群 · MySQL · 30s 自动刷新",
|
||||
"lastUpdate": "上次更新",
|
||||
"addPlatformDefault": "添加平台默认",
|
||||
"noInstancePrefix": "暂无",
|
||||
"instanceSuffix": "实例",
|
||||
"siteMonitorSubtitle": "HTTP Web 拨测 · SSL 证书检测 · TCP / DNS / PING",
|
||||
"snmpMonitorTitle": "SNMP 监控",
|
||||
"snmpCollectInterval": "5 分钟采集 · 指标保留 7 天",
|
||||
"tcpTargetAddr": "TCP 目标地址",
|
||||
"tcpPort": "TCP 端口",
|
||||
"ipAddress": "IP 地址",
|
||||
"snmpOidTemplate": "OID 模板",
|
||||
"snmpVersion": "SNMP 版本",
|
||||
"snmpNoDevice": "暂无 SNMP 设备,点击「添加设备」开始监控",
|
||||
"snmpCollectMetrics": "采集指标",
|
||||
"snmpNoMetrics": "暂无指标,请先采集"
|
||||
},
|
||||
"logAlertBell": {
|
||||
"linked": "已关联告警策略",
|
||||
@@ -7043,7 +7089,7 @@
|
||||
"loadFailed": "加载告警数据失败",
|
||||
"resolveAlert": "恢复告警",
|
||||
"resolveConfirm": "确定将此告警标记为已恢复?",
|
||||
"resolved": "告警已恢复",
|
||||
"resolved": "已恢复",
|
||||
"deleteRule": "删除规则",
|
||||
"thresholdTriggered": "触发告警阈值",
|
||||
"noResolved": "暂无已恢复的告警",
|
||||
@@ -7051,7 +7097,12 @@
|
||||
"metricSwap": "Swap 使用率",
|
||||
"metricLoad1": "1分钟负载",
|
||||
"metricInode": "Inode 使用率",
|
||||
"metricTcpEst": "TCP 已建立"
|
||||
"metricTcpEst": "TCP 已建立",
|
||||
"title": "Agent 告警",
|
||||
"unresolved": "未恢复",
|
||||
"resolve": "恢复",
|
||||
"newRule": "新建规则",
|
||||
"createFirstRule": "创建第一条规则"
|
||||
},
|
||||
"allPortsHint": "全部端口自动上报",
|
||||
"allPortsMode": "全量上报",
|
||||
|
||||
@@ -153,10 +153,10 @@ export function MiddlewareMonitor() {
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold"> {t('monitor.alertHelper.srcMiddleware')} </h2>
|
||||
<p className="text-xs text-[var(--dg-text-muted)] mt-0.5">
|
||||
Redis 单机/集群 · MySQL · 30s 自动刷新
|
||||
{t('monitor.lbl.redisMysqlAutoRefresh')}
|
||||
{lastRefreshAt && (
|
||||
<span className="ml-2">
|
||||
上次更新 {lastRefreshAt.toLocaleTimeString()}
|
||||
{t('monitor.lbl.lastUpdate')} {lastRefreshAt.toLocaleTimeString()}
|
||||
{refreshing && t('monitor.lbl.refreshing')}
|
||||
</span>
|
||||
)}
|
||||
@@ -172,7 +172,7 @@ export function MiddlewareMonitor() {
|
||||
{targets.length === 0 && (
|
||||
<Button variant="outline" size="sm" onClick={handleSeedDefaults} disabled={seeding} className="gap-1.5">
|
||||
{seeding ? <Loader2 className="h-4 w-4 animate-spin" /> : <Database className="h-4 w-4" />}
|
||||
添加平台默认
|
||||
{t('monitor.lbl.addPlatformDefault')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" onClick={() => openCreate()} className="gap-1.5">
|
||||
@@ -252,7 +252,7 @@ export function MiddlewareMonitor() {
|
||||
{currentTargets.length === 0 ? (
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
|
||||
暂无 {tab === 'redis' ? 'Redis' : 'MySQL'} 实例,
|
||||
{t('monitor.lbl.noInstancePrefix')} {tab === 'redis' ? 'Redis' : 'MySQL'} {t('monitor.lbl.instanceSuffix')},
|
||||
<button type="button" className="dg-text-cyan ml-1 underline" onClick={() => openCreate(tab)}> {t('monitor.middlewareMonit.addNow')} </button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -183,8 +183,8 @@ export function SNMPMonitor() {
|
||||
{/* 工具栏 */}
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">SNMP 监控</h2>
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">5 分钟采集 · 指标保留 7 天</p>
|
||||
<h2 className="text-lg font-semibold">{t('monitor.lbl.snmpMonitorTitle')}</h2>
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.lbl.snmpCollectInterval')}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="outline" onClick={refresh} className="h-8">
|
||||
@@ -208,7 +208,7 @@ export function SNMPMonitor() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid gap-1.5">
|
||||
<Label>IP 地址</Label>
|
||||
<Label>{t('monitor.lbl.ipAddress')}</Label>
|
||||
<Input value={form.ip} onChange={(e) => setForm({ ...form, ip: e.target.value })} placeholder="192.168.1.1" />
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
@@ -217,7 +217,7 @@ export function SNMPMonitor() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label>OID 模板</Label>
|
||||
<Label>{t('monitor.lbl.snmpOidTemplate')}</Label>
|
||||
<Select value={form.template_id} onValueChange={(v) => setForm({ ...form, template_id: v })}>
|
||||
<SelectTrigger><SelectValue placeholder={t('pipeline.selectTemplate')} /></SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -229,7 +229,7 @@ export function SNMPMonitor() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid gap-1.5">
|
||||
<Label>SNMP 版本</Label>
|
||||
<Label>{t('monitor.lbl.snmpVersion')}</Label>
|
||||
<Select value={form.snmp_version} onValueChange={(v) => setForm({ ...form, snmp_version: v as 'v2c' | 'v3' })}>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -247,7 +247,7 @@ export function SNMPMonitor() {
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}> {t('rbac.jobRole.cancel')} </Button>
|
||||
<Button onClick={handleAdd} disabled={submitting}>
|
||||
{submitting && <Loader2 className="h-4 w-4 animate-spin mr-1" />}确认
|
||||
{submitting && <Loader2 className="h-4 w-4 animate-spin mr-1" />}{t('common.confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -278,7 +278,7 @@ export function SNMPMonitor() {
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="py-12 text-center text-sm text-[var(--dg-text-muted)]">
|
||||
<Router className="h-10 w-10 mx-auto mb-3 opacity-40" />
|
||||
暂无 SNMP 设备,点击「添加设备」开始监控
|
||||
{t('monitor.lbl.snmpNoDevice')}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -348,14 +348,14 @@ export function SNMPMonitor() {
|
||||
<Dialog open={metricsOpen} onOpenChange={setMetricsOpen}>
|
||||
<DialogContent className="sm:max-w-2xl max-h-[80vh] overflow-y-auto bg-[var(--dg-bg-secondary)]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{metricsDevice?.name} — 采集指标</DialogTitle>
|
||||
<DialogTitle>{metricsDevice?.name} — {t('monitor.lbl.snmpCollectMetrics')}</DialogTitle>
|
||||
<DialogDescription>{metricsDevice?.ip} · {templateName(metricsDevice?.template_id || 0)}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{metricsLoading ? (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground"> {t('monitor.logPanel.logResults.loading')} </div>
|
||||
) : metrics.length === 0 ? (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">
|
||||
暂无指标,请先采集
|
||||
{t('monitor.lbl.snmpNoMetrics')}
|
||||
{metricsDevice?.last_error && <p className="mt-2 text-xs dg-text-red">{metricsDevice.last_error}</p>}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -213,7 +213,7 @@ export function SiteMonitor() {
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold"> {t('monitor.alertRulesTab.catSite')} </h2>
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">HTTP Web 拨测 · SSL 证书检测 · TCP / DNS / PING</p>
|
||||
<p className="text-xs text-[var(--dg-text-muted)]">{t('monitor.lbl.siteMonitorSubtitle')}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="outline" className="h-8" onClick={() => { loadStats(); loadTasks(); }} disabled={refreshing}>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ZoomableAreaChart, type ZoomableChartHandle } from './ZoomableAreaChart
|
||||
import { ChartToolbar } from './ChartToolbar';
|
||||
import { MetricIconBox } from './MetricIconBox';
|
||||
import type { AggType, BucketPoint } from './types';
|
||||
import i18n from '@/i18n';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface DualLineChartProps {
|
||||
title: string;
|
||||
@@ -21,6 +21,7 @@ interface DualLineChartProps {
|
||||
export function DualLineChart({
|
||||
title, currentRx, currentTx, rxData, txData, aggType, unit,
|
||||
}: DualLineChartProps) {
|
||||
const { t } = useTranslation();
|
||||
const chartRef = useRef<ZoomableChartHandle>(null);
|
||||
const [zoomed, setZoomed] = useState(false);
|
||||
|
||||
@@ -43,11 +44,11 @@ export function DualLineChart({
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
<span>
|
||||
<span className="inline-block w-2.5 h-2.5 rounded-sm bg-blue-400 mr-1 align-middle" />
|
||||
接收 <b className="text-blue-300">{currentRx}</b>
|
||||
{t('monitor.lbl.rx')} <b className="text-blue-300">{currentRx}</b>
|
||||
</span>
|
||||
<span>
|
||||
<span className="inline-block w-2.5 h-2.5 rounded-sm bg-emerald-400 mr-1 align-middle" />
|
||||
发送 <b className="dg-text-green">{currentTx}</b>
|
||||
{t('monitor.lbl.tx')} <b className="dg-text-green">{currentTx}</b>
|
||||
</span>
|
||||
</div>
|
||||
<ChartToolbar
|
||||
@@ -61,14 +62,14 @@ export function DualLineChart({
|
||||
ref={chartRef}
|
||||
data={chartData}
|
||||
series={[
|
||||
{ dataKey: 'rx', color: '#3b82f6', label: i18n.t('common.receive') },
|
||||
{ dataKey: 'tx', color: '#10b981', label: i18n.t('aiops.sendMessage') },
|
||||
{ dataKey: 'rx', color: '#3b82f6', label: t('common.receive') },
|
||||
{ dataKey: 'tx', color: '#10b981', label: t('aiops.sendMessage') },
|
||||
]}
|
||||
yDomain={[0, 'auto']}
|
||||
unit={unit}
|
||||
chartId={`dual-${title.replace(/\s/g, '')}`}
|
||||
title={title}
|
||||
subtitle={i18n.t('monitor.lbl.receiveSend', { rx: currentRx, tx: currentTx })}
|
||||
subtitle={t('monitor.lbl.receiveSend', { rx: currentRx, tx: currentTx })}
|
||||
height={208}
|
||||
toolbarPlacement="header"
|
||||
onZoomedChange={setZoomed}
|
||||
|
||||
@@ -200,9 +200,9 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
|
||||
{probeForm.probe_type === 'tcp_socket' && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">TCP 目标地址</label>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">{t('monitor.lbl.tcpTargetAddr')}</label>
|
||||
<input className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_host} onChange={e => setProbeForm({ ...probeForm, tcp_host: e.target.value })} placeholder="localhost" /></div>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">TCP 端口</label>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">{t('monitor.lbl.tcpPort')}</label>
|
||||
<input type="number" className="w-full h-8 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 text-xs text-[var(--dg-text-primary)]" value={probeForm.tcp_port} onChange={e => setProbeForm({ ...probeForm, tcp_port: Number(e.target.value) })} /></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-[11px] dg-text-muted hidden sm:inline-flex items-center gap-1">
|
||||
<Timer className="h-3 w-3" />
|
||||
时间范围
|
||||
{t('monitor.lbl.timeRange')}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 rounded-md border border-[var(--dg-border)] p-0.5 bg-[var(--dg-bg-secondary)] flex-wrap">
|
||||
{TIME_RANGE_PRESETS.map((p) => (
|
||||
@@ -82,7 +82,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
onClick={() => setCustomOpen((v) => !v)}
|
||||
>
|
||||
<Calendar className="h-3 w-3" />
|
||||
自定义
|
||||
{t('monitor.lbl.custom')}
|
||||
<ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} />
|
||||
</Button>
|
||||
{customOpen && (
|
||||
@@ -110,10 +110,10 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-3">
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setCustomOpen(false)}>
|
||||
取消
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button type="button" size="sm" className="h-7 text-xs dg-btn-primary" onClick={applyCustom}>
|
||||
应用
|
||||
{t('monitor.lbl.apply')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,13 +43,13 @@ export function PortPolicyDialog({
|
||||
className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'whitelist' ? 'bg-blue-500/20 border-blue-500 dg-text-blue' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`}
|
||||
onClick={() => onModeChange('whitelist')}
|
||||
>
|
||||
白名单 ─ 全量上报
|
||||
{t('monitor.lbl.whitelistFullReport')}
|
||||
</button>
|
||||
<button
|
||||
className={`flex-1 h-9 rounded text-xs font-medium border transition-colors ${portPolicyMode === 'blacklist' ? 'bg-amber-500/20 border-amber-500 dg-text-yellow' : 'bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] text-[var(--dg-text-muted)]'}`}
|
||||
onClick={() => onModeChange('blacklist')}
|
||||
>
|
||||
黑名单 ─ 仅上报选中
|
||||
{t('monitor.lbl.blacklistSelectedOnly')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1.5 leading-relaxed">
|
||||
@@ -61,8 +61,8 @@ export function PortPolicyDialog({
|
||||
|
||||
<div>
|
||||
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1.5 block">
|
||||
端口列表 <span className="text-[var(--dg-text-muted)]">(逗号分隔,如: 80,443,3306,6379)</span>
|
||||
{portPolicyMode === 'whitelist' && <span className="dg-text-blue ml-1">— 白名单模式无需配置</span>}
|
||||
{t('monitor.lbl.portListLabel')} <span className="text-[var(--dg-text-muted)]">{t('monitor.lbl.portListHint')}</span>
|
||||
{portPolicyMode === 'whitelist' && <span className="dg-text-blue ml-1">{t('monitor.lbl.whitelistNoConfig')}</span>}
|
||||
</label>
|
||||
<textarea
|
||||
className={`w-full h-20 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded px-2 py-1.5 text-xs font-mono resize-none ${
|
||||
@@ -78,7 +78,7 @@ export function PortPolicyDialog({
|
||||
readOnly={portPolicyMode === 'whitelist'}
|
||||
/>
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1">
|
||||
已输入 {portWhitelist.length} 个端口
|
||||
{t('monitor.lbl.portsEntered', { count: portWhitelist.length })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ export const ZoomableAreaChart = forwardRef<ZoomableChartHandle, Props>(function
|
||||
className="flex items-center justify-center text-xs dg-text-muted border border-dashed border-[var(--dg-border)] rounded-md w-full"
|
||||
style={{ height }}
|
||||
>
|
||||
暂无数据
|
||||
{t('monitor.lbl.noData')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,18 +16,18 @@ export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) {
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">CTX 切换/秒</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">{t('monitor.lbl.ctxPerSec')}</div>
|
||||
<div className="text-lg font-bold font-mono dg-text-green">
|
||||
{(csInfo.ctx_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.ctx_per_sec?.toLocaleString()} 次</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.ctx_per_sec?.toLocaleString()} {t('monitor.lbl.times')}</div>
|
||||
</div>
|
||||
<div className="bg-[var(--dg-bg-primary)] rounded-lg p-3 text-center">
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">IRQ 中断/秒</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mb-1">{t('monitor.lbl.irqPerSec')}</div>
|
||||
<div className="text-lg font-bold font-mono dg-text-yellow">
|
||||
{(csInfo.irq_per_sec / 1e6).toFixed(1)}<span className="text-xs font-normal text-[var(--dg-text-muted)] ml-0.5">M</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.irq_per_sec?.toLocaleString()} 次</div>
|
||||
<div className="text-[10px] text-[var(--dg-text-muted)] mt-1 font-mono">{csInfo.irq_per_sec?.toLocaleString()} {t('monitor.lbl.times')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
|
||||
@@ -26,7 +26,7 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo
|
||||
<div className="flex items-start justify-between gap-2 mb-3">
|
||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)] flex items-center gap-2 leading-8">
|
||||
<MetricIconBox icon={<HardDrive />} tone="purple" />
|
||||
FD 文件句柄
|
||||
{t('monitor.lbl.fdHandles')}
|
||||
</h3>
|
||||
{fdMetrics.length > 0 && (
|
||||
<ChartToolbar
|
||||
|
||||
@@ -23,22 +23,22 @@ export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpen
|
||||
<span className="ml-auto flex items-center gap-2">
|
||||
{portPolicyMode === 'blacklist' && portWhitelist.length > 0 && (
|
||||
<Badge variant="outline" className="text-[10px] dg-text-yellow border-amber-500/30">
|
||||
黑名单: {portWhitelist.join(',')}
|
||||
{t('monitor.lbl.blacklistLabel')}: {portWhitelist.join(',')}
|
||||
</Badge>
|
||||
)}
|
||||
{portPolicyMode === 'whitelist' && (
|
||||
<Badge variant="outline" className="text-[10px] dg-text-blue border-blue-500/30">
|
||||
白名单模式
|
||||
{t('monitor.lbl.whitelistModeLabel')}
|
||||
</Badge>
|
||||
)}
|
||||
<Button variant="outline" size="sm" className="h-6 text-[10px] px-2" onClick={onOpenPortPolicy}>
|
||||
策略配置
|
||||
{t('monitor.lbl.policyConfig')}
|
||||
</Button>
|
||||
</span>
|
||||
</h3>
|
||||
{portInfo.length === 0 ? (
|
||||
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
|
||||
暂无端口数据
|
||||
{t('monitor.lbl.noPortData')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
|
||||
@@ -14,7 +14,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
|
||||
</h3>
|
||||
{processTop.length === 0 ? (
|
||||
<div className="text-center py-6 text-xs text-[var(--dg-text-muted)] border border-dashed border-[var(--dg-border)] rounded-lg">
|
||||
Agent v2.0+ 支持,等待数据上报…
|
||||
{t('monitor.lbl.agentWaitData')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
@@ -24,7 +24,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
|
||||
<th className="py-1.5 text-left font-medium w-[40%]"> {t('monitor.lbl.processMonitor')} </th>
|
||||
<th className="py-1.5 text-right font-medium w-[8%]">PID</th>
|
||||
<th className="py-1.5 text-right font-medium w-[16%]"> {t('monitor.lbl.processMonitor')} </th>
|
||||
<th className="py-1.5 text-right font-medium w-[16%]">CPU使用率(%)</th>
|
||||
<th className="py-1.5 text-right font-medium w-[16%]">{t('monitor.lbl.cpuUsagePercent')}</th>
|
||||
<th className="py-1.5 text-right font-medium w-[20%]"> {t('monitor.lbl.processMonitor')} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -98,7 +98,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
if (!task) {
|
||||
const empty = (
|
||||
<div className="p-8 text-center text-sm text-[var(--dg-text-muted)]">
|
||||
选择任务查看协议专属可观测指标
|
||||
{t('monitor.lbl.selectTaskHint')}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
@@ -218,7 +218,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
</>
|
||||
)}
|
||||
<span className="text-[10px] text-[var(--dg-text-muted)] shrink-0">
|
||||
{rangeLabel} · {results.length} 次 · 可用率 {availSummary}
|
||||
{rangeLabel} · {results.length} {t('monitor.lbl.times')} · {t('monitor.lbl.availabilityRate')} {availSummary}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
@@ -235,7 +235,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? 'animate-spin' : ''}`} />{t("common.refresh")}
|
||||
</Button>
|
||||
<Button size="sm" className="h-7 text-xs" onClick={handleProbeNow} disabled={probing}>
|
||||
{probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}立即拨测
|
||||
{probing ? <Loader2 className="h-3 w-3 mr-1 animate-spin" /> : <Zap className="h-3 w-3 mr-1" />}{t('monitor.lbl.probeNow')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,9 +244,9 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<div className="mb-4 flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2.5 text-xs">
|
||||
<ShieldAlert className="h-4 w-4 shrink-0 mt-0.5 dg-text-yellow" />
|
||||
<div>
|
||||
<p className="font-medium text-[var(--dg-text-primary)]">SSL 证书预警</p>
|
||||
<p className="font-medium text-[var(--dg-text-primary)]">{t('monitor.lbl.sslCertWarning')}</p>
|
||||
<p className="mt-0.5 text-[var(--dg-text-muted)]">
|
||||
到期 <span className="font-mono">{task.ssl_cert_expiry?.slice(0, 10) || '--'}</span>,剩余 <span className={sslInfo.tone}>{sslInfo.text}</span>(阈值 {SSL_EXPIRY_WARN_DAYS} 天)
|
||||
{t('monitor.lbl.sslExpiring')} <span className="font-mono">{task.ssl_cert_expiry?.slice(0, 10) || '--'}</span>,{t('monitor.lbl.sslRemaining')} <span className={sslInfo.tone}>{sslInfo.text}</span>({t('monitor.lbl.sslThreshold')} {SSL_EXPIRY_WARN_DAYS} {t('monitor.lbl.sslDays')})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user