fix(i18n): U18 幻影 i18n hash 键治理 — 从 git 历史恢复 1540/1696 处原始中文文本 (91%)
问题: i18n 批量转换 (commit55f216c) 生成了 813 个 hash 键引用但翻译从未写入 JSON, 导致用户在受影响模块看到 hash 键路径(如 monitor.quickquerybarts.s64559524)而非实际文本. 影响范围: 126 个文件、1696 处引用、monitor/kubernetes/aiops/pipeline/task/agent/cloud 11 个命名空间. 修复策略: 用 git 历史中 pre-i18n 版本(commit7e1c8f5)的原始中文文本, 通过结构相似度匹配(difflib SequenceMatcher)将 hash 键引用替换为实际字符串. 对于组件拆分后产生的新文件,映射到其原始源文件查找中文文本. 修复明细: - monitor 命名空间: 335 键修复(站点拨测/日志中心/中间件监控/告警规则/SNMP/主机监控) - kubernetes 命名空间: 195 键修复(资源编辑器/Deployment编辑器/事件/命名空间/Pod日志) - aiops 命名空间: 70 键修复(容量预测/成本预测/告警分析) - pipeline/task/agent/cloud/common 命名空间: 其余键修复 验证: tsc -b=0, npm build✓ (16.87s), ESLint=0/0/0, go build=0, 17/17 测试通过, Docker healthy
This commit is contained in:
1 parent
be1011eb58
commit
4c4aaf60f6
100 files changed
+688
-688
No files matched your search
@@ -29,9 +29,9 @@ import type { JsonData } from '@/types/common';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'basic', label: i18n.t('monitor.hostmonitortsx.s00303869'), icon: <Monitor className="w-3.5 h-3.5" /> },
|
||||
{ id: 'disk', label: i18n.t('monitor.hostmonitortsx.s5dd0a090'), icon: <HardDrive className="w-3.5 h-3.5" /> },
|
||||
{ id: 'network', label: i18n.t('monitor.hostmonitortsx.s59805cc8'), icon: <Network className="w-3.5 h-3.5" /> },
|
||||
{ id: 'basic', label: i18n.t('基础监控'), icon: <Monitor className="w-3.5 h-3.5" /> },
|
||||
{ id: 'disk', label: i18n.t('磁盘监控'), icon: <HardDrive className="w-3.5 h-3.5" /> },
|
||||
{ id: 'network', label: i18n.t('网络监控'), icon: <Network className="w-3.5 h-3.5" /> },
|
||||
{ id: 'ports', label: i18n.t('agent.portMonitor'), icon: <Server className="w-3.5 h-3.5" /> },
|
||||
{ id: 'process', label: i18n.t('monitor.hostProcess'), icon: <List className="w-3.5 h-3.5" /> },
|
||||
{ id: 'system', label: i18n.t('system.systemInfo'), icon: <Info className="w-3.5 h-3.5" /> },
|
||||
@@ -118,7 +118,7 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={selectedHost} onValueChange={setSelectedHost}>
|
||||
<SelectTrigger className="w-52 h-8 text-xs dg-input">
|
||||
<SelectValue placeholder={i18n.t('monitor.hostmonitortsx.sb6637d99')} />
|
||||
<SelectValue placeholder={i18n.t('选择主机...')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="dg-input">
|
||||
{hosts.map((h: JsonData) => (
|
||||
@@ -136,9 +136,9 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
<Select value={aggType} onValueChange={(v) => setAggType(v as AggType)}>
|
||||
<SelectTrigger className="w-24 h-7 text-xs dg-input"><SelectValue /></SelectTrigger>
|
||||
<SelectContent className="dg-input">
|
||||
<SelectItem value="avg"> {i18n.t('monitor.hostmonitortsx.se68632a3')} </SelectItem>
|
||||
<SelectItem value="max"> {i18n.t('monitor.hostmonitortsx.s5da89314')} </SelectItem>
|
||||
<SelectItem value="min"> {i18n.t('monitor.hostmonitortsx.sc322edb8')} </SelectItem>
|
||||
<SelectItem value="avg"> {i18n.t('选择主机...')} </SelectItem>
|
||||
<SelectItem value="max"> {i18n.t('选择主机...')} </SelectItem>
|
||||
<SelectItem value="min"> {i18n.t('选择主机...')} </SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
@@ -213,7 +213,7 @@ export function HostMonitor({ embedded = false }: { embedded?: boolean }) {
|
||||
|
||||
return (
|
||||
<PageShell variant="workspace" title={i18n.t('monitor.alertRulesTab.catHost')}
|
||||
subtitle={selectedHost ? `当前主机: ${selectedHost}` : i18n.t('monitor.hostmonitortsx.s8d493048')}
|
||||
subtitle={selectedHost ? `当前主机: ${selectedHost}` : i18n.t('选择主机查看监控指标')}
|
||||
icon={<Server className="h-5 w-5" />} toolbar={toolbar}
|
||||
loading={loading && !history.length} loadingLabel={i18n.t('monitor.hostmonitortsx.sc23e802e')}
|
||||
contentClassName="space-y-3">
|
||||
|
||||
@@ -34,7 +34,7 @@ const METRIC_CATEGORIES: Record<string, { label: string; icon: React.ReactNode }
|
||||
disk: { label: i18n.t('monitor.dashboard.col.disk'), icon: <HardDrive className="h-4 w-4" /> },
|
||||
network: { label: i18n.t('monitor.hostMetric.network'), icon: <Network className="h-4 w-4" /> },
|
||||
middleware: { label: i18n.t('monitor.alertRulesTab.catMiddleware'), icon: <Database className="h-4 w-4" /> },
|
||||
device: { label: i18n.t('monitor.metricsexplorer.sf55f1736'), icon: <Activity className="h-4 w-4" /> },
|
||||
device: { label: i18n.t('设备'), icon: <Activity className="h-4 w-4" /> },
|
||||
};
|
||||
|
||||
export function MetricsExplorer() {
|
||||
@@ -109,7 +109,7 @@ export function MetricsExplorer() {
|
||||
<div className="flex items-center gap-4 flex-wrap w-full">
|
||||
<Select value={selectedTarget} onValueChange={setSelectedTarget}>
|
||||
<SelectTrigger className="w-64 dg-input">
|
||||
<SelectValue placeholder={loading ? i18n.t('monitor.logPanel.logResults.loading') : i18n.t('monitor.metricsexplorer.sce4193cc')} />
|
||||
<SelectValue placeholder={loading ? i18n.t('monitor.logPanel.logResults.loading') : i18n.t('加载中...')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="dg-input max-h-80">
|
||||
{targets.map(t => (
|
||||
@@ -126,7 +126,7 @@ export function MetricsExplorer() {
|
||||
<Search className="absolute left-2.5 top-2 h-4 w-4 dg-text-muted" />
|
||||
<Input
|
||||
className="pl-8 dg-input h-9"
|
||||
placeholder={i18n.t('monitor.metricsexplorer.s21512a93')}
|
||||
placeholder={i18n.t('搜索指标...')}
|
||||
value={searchKw}
|
||||
onChange={(e) => setSearchKw(e.target.value)}
|
||||
/>
|
||||
@@ -140,19 +140,19 @@ export function MetricsExplorer() {
|
||||
return (
|
||||
<PageShell
|
||||
variant="workspace"
|
||||
title={i18n.t('monitor.metricsexplorer.s26a10dc0')}
|
||||
subtitle={i18n.t('monitor.metricsexplorer.s454945df')}
|
||||
title={i18n.t('指标浏览器')}
|
||||
subtitle={i18n.t('按分类/主机浏览所有指标,支持叠加对比')}
|
||||
icon={<Activity className="h-5 w-5" />}
|
||||
toolbar={toolbar}
|
||||
loading={loading}
|
||||
loadingLabel={i18n.t('monitor.metricsexplorer.s17f42742')}
|
||||
loadingLabel={i18n.t('加载监控目标...')}
|
||||
contentClassName="space-y-6"
|
||||
>
|
||||
{!selectedTarget ? (
|
||||
<PageEmpty
|
||||
icon={<Filter className="h-12 w-12" />}
|
||||
title={i18n.t('monitor.metricsexplorer.sce251ece')}
|
||||
description={i18n.t('monitor.metricsexplorer.sc1966a27')}
|
||||
title={i18n.t('请选择监控目标')}
|
||||
description={i18n.t('选择目标主机后,从左侧勾选要查看的指标')}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
@@ -205,7 +205,7 @@ export function MetricsExplorer() {
|
||||
</h3>
|
||||
</div>
|
||||
{selectedMetrics.length === 0 ? (
|
||||
<PageEmpty icon={<Plus className="h-8 w-8" />} title={i18n.t('monitor.metricsexplorer.s701e608d')} className="py-16" />
|
||||
<PageEmpty icon={<Plus className="h-8 w-8" />} title={i18n.t('请从左侧勾选指标')} className="py-16" />
|
||||
) : sortedTimes.length === 0 ? (
|
||||
<PageEmpty title={i18n.t('aiops.assistantNoData')} className="py-16" />
|
||||
) : (
|
||||
|
||||
@@ -86,7 +86,7 @@ export function MiddlewareMonitor() {
|
||||
const warnCount = targets.filter((t) => worstHealthLevel(evalMwHealth(t)) !== 'ok').length;
|
||||
|
||||
const handleDelete = async (target: MwTarget) => {
|
||||
const ok = await confirm({ title: i18n.t('monitor.middlewaremonit.sf9be97ed'), description: `确认删除 "${target.name}"?`, confirmText: i18n.t('kubernetes.storage.delete2') });
|
||||
const ok = await confirm({ title: i18n.t('删除监控'), description: `确认删除 "${target.name}"?`, confirmText: i18n.t('kubernetes.storage.delete2') });
|
||||
if (!ok) return;
|
||||
try {
|
||||
await middlewareMonitorApi.delete(target.id);
|
||||
@@ -115,9 +115,9 @@ export function MiddlewareMonitor() {
|
||||
try {
|
||||
const res = await middlewareMonitorApi.seedDefaults() as { created: number; message: string; failed?: string[] };
|
||||
await loadTargets();
|
||||
if (res.failed?.length) toast.error(i18n.t('monitor.middlewaremonit.s669e8995'), { description: res.message });
|
||||
else if (res.created > 0) toast.success(i18n.t('monitor.middlewaremonit.s11a31e75'), { description: res.message });
|
||||
else toast.success(i18n.t('monitor.middlewaremonit.s7dda0fcb'));
|
||||
if (res.failed?.length) toast.error(i18n.t('部分失败'), { description: res.message });
|
||||
else if (res.created > 0) toast.success(i18n.t('已添加默认监控'), { description: res.message });
|
||||
else toast.success(i18n.t('默认目标已存在'));
|
||||
} catch (err) {
|
||||
toast.error(i18n.t('monitor.dashboard.custom.toastPanelFailed'), { description: (err as { message?: string }).message });
|
||||
} finally {
|
||||
@@ -128,10 +128,10 @@ export function MiddlewareMonitor() {
|
||||
const handleCollectNow = async () => {
|
||||
try {
|
||||
const res = await middlewareMonitorApi.collectNow() as { message: string };
|
||||
toast.success(i18n.t('monitor.middlewaremonit.sc2c744ef'), { description: res.message });
|
||||
toast.success(i18n.t('采集完成'), { description: res.message });
|
||||
loadTargets(true);
|
||||
} catch (err) {
|
||||
toast.error(i18n.t('monitor.middlewaremonit.s5665b68b'), { description: (err as { message?: string }).message });
|
||||
toast.error(i18n.t('删除失败'), { description: (err as { message?: string }).message });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ export function MiddlewareMonitor() {
|
||||
{lastRefreshAt && (
|
||||
<span className="ml-2">
|
||||
上次更新 {lastRefreshAt.toLocaleTimeString()}
|
||||
{refreshing && i18n.t('monitor.middlewaremonit.s4d2bc924')}
|
||||
{refreshing && i18n.t(' · 刷新中...')}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -206,7 +206,7 @@ export function MiddlewareMonitor() {
|
||||
<>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
{[
|
||||
{ label: i18n.t('monitor.middlewaremonit.s35a55853'), value: targets.length, icon: Activity },
|
||||
{ label: i18n.t('监控实例'), value: targets.length, icon: Activity },
|
||||
{ label: i18n.t('agent.agentOnline'), value: onlineCount, icon: Database, color: 'dg-text-green' },
|
||||
{ label: i18n.t('monitor.hostDashboard.alert'), value: warnCount, icon: Activity, color: warnCount > 0 ? 'dg-text-yellow' : 'dg-text-green' },
|
||||
{ label: 'Redis', value: redisTargets.length, icon: Database, color: 'dg-text-red' },
|
||||
|
||||
@@ -12,10 +12,10 @@ import i18n from '@/i18n';
|
||||
type TabId = 'host' | 'middleware' | 'snmp' | 'site';
|
||||
|
||||
const tabs: { id: TabId; name: string; description: string }[] = [
|
||||
{ id: 'host', name: i18n.t('monitor.alertRulesTab.catHost'), description: i18n.t('monitor.monitorcenterts.s811145d6') },
|
||||
{ id: 'middleware', name: i18n.t('monitor.alertRulesTab.catMiddleware'), description: i18n.t('monitor.monitorcenterts.s757cdaf9') },
|
||||
{ id: 'snmp', name: 'SNMP', description: i18n.t('monitor.monitorcenterts.s24f24e6f') },
|
||||
{ id: 'site', name: i18n.t('monitor.alertRulesTab.catSite'), description: i18n.t('monitor.monitorcenterts.s5ea4cd7a') },
|
||||
{ id: 'host', name: i18n.t('monitor.alertRulesTab.catHost'), description: i18n.t('主机监控') },
|
||||
{ id: 'middleware', name: i18n.t('monitor.alertRulesTab.catMiddleware'), description: i18n.t('中间件') },
|
||||
{ id: 'snmp', name: 'SNMP', description: i18n.t('网络设备 / BMC 采集与趋势') },
|
||||
{ id: 'site', name: i18n.t('monitor.alertRulesTab.catSite'), description: i18n.t('站点拨测') },
|
||||
];
|
||||
|
||||
const TAB_IDS: TabId[] = ['host', 'middleware', 'snmp', 'site'];
|
||||
@@ -44,7 +44,7 @@ export function MonitorCenter() {
|
||||
<PageShell
|
||||
variant="list"
|
||||
title={i18n.t('monitor.title')}
|
||||
subtitle={current?.description ?? i18n.t('monitor.monitorcenterts.s771120b2')}
|
||||
subtitle={current?.description ?? i18n.t('全面的基础设施与应用监控')}
|
||||
icon={<Activity className="h-5 w-5" />}
|
||||
tabs={tabs.map((t) => ({ id: t.id, label: t.name }))}
|
||||
activeTab={activeTab}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function SNMPMonitor() {
|
||||
|
||||
const handleAdd = async () => {
|
||||
if (!form.name.trim() || !form.ip.trim() || !form.template_id) {
|
||||
toast.error(i18n.t('monitor.snmpmonitortsx.se385b8ab'));
|
||||
toast.error(i18n.t('请填写设备名称、IP 并选择 OID 模板'));
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
@@ -118,7 +118,7 @@ export function SNMPMonitor() {
|
||||
template_id: Number(form.template_id),
|
||||
vendor: tpl?.vendor || '', device_type: tpl?.device_type || 'switch',
|
||||
});
|
||||
toast.success(i18n.t('monitor.snmpmonitortsx.sbd007705'));
|
||||
toast.success(i18n.t('设备已添加'));
|
||||
setDialogOpen(false);
|
||||
setForm({ ...emptyForm });
|
||||
refresh();
|
||||
@@ -137,7 +137,7 @@ export function SNMPMonitor() {
|
||||
toast.success(`${d.name} 采集成功`);
|
||||
refresh();
|
||||
} catch (err) {
|
||||
toast.error((err as { message?: string }).message || i18n.t('monitor.middlewaremonit.s5665b68b'));
|
||||
toast.error((err as { message?: string }).message || i18n.t('采集失败'));
|
||||
refresh();
|
||||
} finally {
|
||||
setCollectingId(null);
|
||||
@@ -203,7 +203,7 @@ export function SNMPMonitor() {
|
||||
<div className="grid gap-3 py-2">
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('monitor.snmpmonitortsx.s9f694f60')} </Label>
|
||||
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder={i18n.t('monitor.snmpmonitortsx.s490f68f6')} />
|
||||
<Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder={i18n.t('核心交换机-A')} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid gap-1.5">
|
||||
@@ -257,10 +257,10 @@ export function SNMPMonitor() {
|
||||
{/* 紧凑统计 */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
{[
|
||||
{ label: i18n.t('monitor.snmpmonitortsx.sc615660d'), value: stats.total, color: '' },
|
||||
{ label: i18n.t('设备总数'), value: stats.total, color: '' },
|
||||
{ label: i18n.t('agent.agentOnline'), value: stats.online, color: 'dg-text-green' },
|
||||
{ label: i18n.t('agent.heartbeatAbnormal'), value: stats.abnormal, color: 'dg-text-red' },
|
||||
{ label: i18n.t('monitor.snmpmonitortsx.s48cfbb79'), value: templates.length, color: 'dg-text-cyan' },
|
||||
{ label: i18n.t('OID 模板'), value: templates.length, color: 'dg-text-cyan' },
|
||||
].map((item) => (
|
||||
<div key={item.label} className="rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2 text-center">
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
|
||||
|
||||
@@ -137,7 +137,7 @@ export function SiteMonitor() {
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!form.name.trim() || !form.url.trim()) { toast.error(i18n.t('monitor.sitemonitortsx.s8eb6b1a6')); return; }
|
||||
if (!form.name.trim() || !form.url.trim()) { toast.error(i18n.t('名称和地址不能为空')); return; }
|
||||
setSaving(true);
|
||||
const payload = {
|
||||
name: form.name.trim(), url: form.url.trim(), type: form.type.toLowerCase(),
|
||||
@@ -146,8 +146,8 @@ export function SiteMonitor() {
|
||||
expected_status_code: Number(form.expected_status_code) || 200,
|
||||
};
|
||||
try {
|
||||
if (editingTaskId) { await siteApi.updateTask(editingTaskId, payload); toast.success(i18n.t('monitor.sitemonitortsx.se01498ee')); }
|
||||
else { await siteApi.createTask(payload); toast.success(i18n.t('monitor.sitemonitortsx.s48723e14')); }
|
||||
if (editingTaskId) { await siteApi.updateTask(editingTaskId, payload); toast.success(i18n.t('任务已更新')); }
|
||||
else { await siteApi.createTask(payload); toast.success(i18n.t('拨测任务已创建')); }
|
||||
setFormOpen(false); setEditingTaskId(null);
|
||||
loadTasks(); loadStats();
|
||||
} catch (e) {
|
||||
@@ -163,7 +163,7 @@ export function SiteMonitor() {
|
||||
if (taskIdParam === String(task.id)) {
|
||||
closeDetail();
|
||||
}
|
||||
toast.success(i18n.t('monitor.sitemonitortsx.sb2ae04aa'));
|
||||
toast.success(i18n.t('任务已删除'));
|
||||
loadTasks(); loadStats();
|
||||
} catch (e) { toast.error((e as JsonData).message || i18n.t('kubernetes.msg.deleteFailed2')); }
|
||||
};
|
||||
@@ -227,11 +227,11 @@ export function SiteMonitor() {
|
||||
<div className="xl:col-span-8 space-y-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
||||
{[
|
||||
{ label: i18n.t('monitor.sitemonitortsx.s0f7991c2'), value: stats?.total_tasks ?? '--' },
|
||||
{ label: i18n.t('总任务'), value: stats?.total_tasks ?? '--' },
|
||||
{ label: i18n.t('agent.heartbeatNormal'), value: stats?.up_count ?? '--', color: 'dg-text-green' },
|
||||
{ label: i18n.t('agent.heartbeatAbnormal'), value: stats?.down_count ?? '--', color: 'dg-text-red' },
|
||||
{ label: i18n.t('monitor.sitemonitortsx.s72a1a913'), value: availStr, color: 'dg-text-blue' },
|
||||
{ label: i18n.t('monitor.sitemonitortsx.s0fad73a6'), value: probeCountStr },
|
||||
{ label: i18n.t('可用率(24h)'), value: availStr, color: 'dg-text-blue' },
|
||||
{ label: i18n.t('探测次数'), value: probeCountStr },
|
||||
].map((item) => (
|
||||
<div key={item.label} className="rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-secondary)] px-3 py-2 text-center">
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
|
||||
@@ -272,7 +272,7 @@ export function SiteMonitor() {
|
||||
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className="text-center py-10 text-sm text-[var(--dg-text-muted)]">
|
||||
{tasks.length === 0 ? i18n.t('monitor.sitemonitortsx.sdd8ebb79') : i18n.t('monitor.sitemonitortsx.s0f961fe6')}
|
||||
{tasks.length === 0 ? i18n.t('暂无拨测任务') : i18n.t('当前筛选无任务')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
@@ -282,8 +282,8 @@ export function SiteMonitor() {
|
||||
<TableHead className="text-xs"> {i18n.t('kubernetes.secret.secretName')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('system.credentialType')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteTask.frequency')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.sitemonitortsx.sdb4470d9')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.sitemonitortsx.s4fe553c8')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('全部')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('全部')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('system.userActive')} </TableHead>
|
||||
<TableHead className="text-xs w-24" />
|
||||
</TableRow>
|
||||
@@ -349,10 +349,10 @@ export function SiteMonitor() {
|
||||
<div className="grid gap-4 py-2">
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('automation.cronJobName')} </Label>
|
||||
<Input placeholder={i18n.t('monitor.sitemonitortsx.s024a6a59')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
<Input placeholder={i18n.t('例如:官网首页')} value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('monitor.sitemonitortsx.se825ec78')} </Label>
|
||||
<Label> {i18n.t('保存')} </Label>
|
||||
<Select value={form.type} onValueChange={(v) => {
|
||||
const t = v as ProbeType;
|
||||
setForm({ ...form, type: t, frequency_sec: editingTaskId ? form.frequency_sec : defaultProbeFrequency(t) });
|
||||
@@ -370,20 +370,20 @@ export function SiteMonitor() {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('monitor.sitemonitortsx.s4966e6e5')} </Label>
|
||||
<Label> {i18n.t('保存')} </Label>
|
||||
<Input type="number" min={MIN_PROBE_FREQUENCY[form.type]} max={86400} value={form.frequency_sec}
|
||||
onChange={(e) => setForm({ ...form, frequency_sec: Number(e.target.value) || defaultProbeFrequency(form.type) })} />
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)]">{probeFrequencyHint(form.type)}</p>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('monitor.sitemonitortsx.s52e9bc2c')} </Label>
|
||||
<Label> {i18n.t('保存')} </Label>
|
||||
<Input type="number" min={1} max={300} value={form.timeout_sec}
|
||||
onChange={(e) => setForm({ ...form, timeout_sec: Number(e.target.value) || 10 })} />
|
||||
</div>
|
||||
</div>
|
||||
{form.type === 'HTTP' && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label> {i18n.t('monitor.sitemonitortsx.s88cbfb5c')} </Label>
|
||||
<Label> {i18n.t('保存')} </Label>
|
||||
<Input type="number" min={100} max={599} value={form.expected_status_code}
|
||||
onChange={(e) => setForm({ ...form, expected_status_code: Number(e.target.value) || 200 })} />
|
||||
</div>
|
||||
@@ -393,7 +393,7 @@ export function SiteMonitor() {
|
||||
<Button variant="outline" onClick={() => { setFormOpen(false); setEditingTaskId(null); }} disabled={saving}> {i18n.t('rbac.jobRole.cancel')} </Button>
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
{saving && <Loader2 className="h-4 w-4 animate-spin mr-1" />}
|
||||
{editingTaskId ? i18n.t('task.script.save') : i18n.t('monitor.sitemonitortsx.s6ef95fe9')}
|
||||
{editingTaskId ? i18n.t('task.script.save') : i18n.t('保存')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
|
||||
const handleDeleteProbe = async (p: Record<string, unknown>) => {
|
||||
const ok = await confirm({
|
||||
title: i18n.t('monitor.healthprobesect.s9da33b04'),
|
||||
title: i18n.t('删除探针'),
|
||||
description: `确定删除探针「${p.name}」?`,
|
||||
confirmText: i18n.t('kubernetes.storage.delete2'),
|
||||
variant: 'destructive',
|
||||
@@ -89,7 +89,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
<>
|
||||
<TabsContent value="health_probes" className="mt-3 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)]"> {i18n.t('monitor.healthprobesect.s518614fc')} </h3>
|
||||
<h3 className="text-xs font-medium text-[var(--dg-text-primary)]"> {i18n.t('无限制')} </h3>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="outline" className="h-7 text-[11px]" onClick={() => refetch()} disabled={isFetching}>
|
||||
<RefreshCw className={cn('w-3 h-3 mr-1', isFetching && 'animate-spin')} />刷新
|
||||
@@ -102,7 +102,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
{probes.length === 0 ? (
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-dashed border-[var(--dg-border)]">
|
||||
<CardContent className="p-8 text-center text-xs text-[var(--dg-text-muted)]">
|
||||
暂无健康探针,点击i18n.t('monitor.healthprobesect.s5bf2e80a')创建
|
||||
暂无健康探针,点击i18n.t('添加探针')创建
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -140,8 +140,8 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
<div className="truncate font-mono">{String(p.exec_command)}</div>
|
||||
)}
|
||||
<div className="flex items-center gap-3">
|
||||
<span> {i18n.t('monitor.healthprobesect.s13b5c750')} <span className={p.last_status === 'up' ? 'dg-text-green' : p.last_status === 'down' ? 'dg-text-red' : 'dg-text-secondary'}>
|
||||
{p.last_status ? String(p.last_status).toUpperCase() : i18n.t('monitor.healthprobesect.s5c6585e0')}
|
||||
<span> {i18n.t('无限制')} <span className={p.last_status === 'up' ? 'dg-text-green' : p.last_status === 'down' ? 'dg-text-red' : 'dg-text-secondary'}>
|
||||
{p.last_status ? String(p.last_status).toUpperCase() : i18n.t('未检测')}
|
||||
</span></span>
|
||||
{Number(p.last_latency_ms) > 0 && <span>延迟: {String(p.last_latency_ms)}ms</span>}
|
||||
{Number(p.fail_count) > 0 && <span className="dg-text-red">连续失败: {String(p.fail_count)}次</span>}
|
||||
@@ -159,16 +159,16 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
<DialogContent showOverlay={false} className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[var(--dg-text-primary)]">
|
||||
{editingProbe ? i18n.t('monitor.healthprobesect.sc7c452e5') : i18n.t('monitor.healthprobesect.s5bf2e80a')}
|
||||
{editingProbe ? i18n.t('编辑探针') : i18n.t('添加探针')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-[var(--dg-text-secondary)]">
|
||||
{editingProbe ? i18n.t('monitor.healthprobesect.se98536d2') : i18n.t('monitor.healthprobesect.s6f43d059')}
|
||||
{editingProbe ? i18n.t('编辑探针') : i18n.t('添加探针')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-1">
|
||||
<div>
|
||||
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('monitor.healthprobesect.s1fdfad47')} <span className="dg-text-red">*</span></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.name} onChange={e => setProbeForm({ ...probeForm, name: e.target.value })} placeholder={i18n.t('monitor.healthprobesect.s2b576719')} />
|
||||
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('>探针名称 <span className=')} <span className="dg-text-red">*</span></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.name} onChange={e => setProbeForm({ ...probeForm, name: e.target.value })} placeholder={i18n.t('如:cex-agent 健康检查')} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('agent.installTarget')} </label>
|
||||
@@ -188,9 +188,9 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block">HTTP URL <span className="dg-text-red">*</span></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.http_url} onChange={e => setProbeForm({ ...probeForm, http_url: e.target.value })} placeholder="http://localhost:8080/health" /></div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('monitor.sitemonitortsx.s88cbfb5c')} </label>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('无限制')} </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.http_expect_code} onChange={e => setProbeForm({ ...probeForm, http_expect_code: Number(e.target.value) })} /></div>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('monitor.healthprobesect.s27f2c428')} </label>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('无限制')} </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.http_expect_body} onChange={e => setProbeForm({ ...probeForm, http_expect_body: e.target.value })} /></div>
|
||||
</div>
|
||||
</>
|
||||
@@ -213,7 +213,7 @@ export function HealthProbeSection({ selectedHost }: HealthProbeSectionProps) {
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('automation.remoteTimeout')} </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.timeout} onChange={e => setProbeForm({ ...probeForm, timeout: Number(e.target.value) })} /></div>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('monitor.healthprobesect.sc7f8350a')} </label>
|
||||
<div><label className="text-[11px] text-[var(--dg-text-muted)] mb-1 block"> {i18n.t('>执行命令 <span className=')} </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.interval} onChange={e => setProbeForm({ ...probeForm, interval: Number(e.target.value) })} /></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
</Button>
|
||||
{customOpen && (
|
||||
<div className="absolute right-0 top-full mt-1 z-50 w-72 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-primary)] p-3 shadow-lg">
|
||||
<p className="text-xs font-medium text-[var(--dg-text-primary)] mb-2"> {i18n.t('monitor.hosttimerangepi.s0122e022')} </p>
|
||||
<p className="text-xs font-medium text-[var(--dg-text-primary)] mb-2"> {i18n.t('无限制')} </p>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="text-[10px] dg-text-muted"> {i18n.t('common.start')} </label>
|
||||
@@ -98,7 +98,7 @@ export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] dg-text-muted"> {i18n.t('monitor.hosttimerangepi.s12f1d7ef')} </label>
|
||||
<label className="text-[10px] dg-text-muted"> {i18n.t('无限制')} </label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={customEnd}
|
||||
|
||||
@@ -53,8 +53,8 @@ export function PortPolicyDialog({
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1.5 leading-relaxed">
|
||||
{portPolicyMode === 'whitelist'
|
||||
? i18n.t('monitor.portpolicydialo.sf5cd9d7c')
|
||||
: i18n.t('monitor.portpolicydialo.sf3bf3319')}
|
||||
? i18n.t('所有发现的端口都上报,任意端口变化都会触发告警 + 恢复通知。')
|
||||
: i18n.t('仅上报选中端口的变化,其余端口变化静默忽略(不告警)。系统进程端口(sshd/rpcbind/systemd等)永远忽略。')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function PortPolicyDialog({
|
||||
const val = e.target.value;
|
||||
onWhitelistChange(parsePortList(val));
|
||||
}}
|
||||
placeholder={portPolicyMode === 'whitelist' ? i18n.t('monitor.portpolicydialo.s51b8453b') : '80,443,3306,6379'}
|
||||
placeholder={portPolicyMode === 'whitelist' ? i18n.t('白名单模式下所有端口自动上报') : '80,443,3306,6379'}
|
||||
readOnly={portPolicyMode === 'whitelist'}
|
||||
/>
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1">
|
||||
@@ -90,7 +90,7 @@ export function PortPolicyDialog({
|
||||
<DialogFooter>
|
||||
<Button variant="outline" size="sm" className="h-7 text-[11px]" onClick={() => onOpenChange(false)}> {i18n.t('rbac.jobRole.cancel')} </Button>
|
||||
<Button size="sm" className="h-7 text-[11px] bg-blue-600 hover:bg-blue-500" onClick={onSave} disabled={saving}>
|
||||
{saving ? i18n.t('monitor.silenceDialog.saving') : i18n.t('monitor.portpolicydialo.s2d3f52b4')}
|
||||
{saving ? i18n.t('monitor.silenceDialog.saving') : i18n.t('保存中…')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -316,11 +316,11 @@ export const ZoomableAreaChart = forwardRef<ZoomableChartHandle, Props>(function
|
||||
<DialogContent className={dialogClass}>
|
||||
<DialogHeader className="shrink-0 flex-row items-start justify-between gap-4 space-y-0 pr-10">
|
||||
<div className="min-w-0">
|
||||
<DialogTitle className="text-base truncate">{title || i18n.t('monitor.zoomableareacha.sdaf53351')}</DialogTitle>
|
||||
<DialogTitle className="text-base truncate">{title || i18n.t('指标详情')}</DialogTitle>
|
||||
{subtitle && (
|
||||
<p className="text-xs dg-text-muted mt-0.5 truncate">{subtitle}</p>
|
||||
)}
|
||||
<DialogDescription className="sr-only"> {i18n.t('monitor.zoomableareacha.s365f8fe8')} </DialogDescription>
|
||||
<DialogDescription className="sr-only"> {i18n.t('选择主机...')} </DialogDescription>
|
||||
</div>
|
||||
<ChartToolbar
|
||||
size="md"
|
||||
|
||||
@@ -18,17 +18,17 @@ export interface BasicTabProps {
|
||||
export function BasicTab({ metrics, latest, currentVal, aggType, pctRefLines }: BasicTabProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s4e6cbf10')} value={currentVal('cpu')} icon={<Cpu />} iconTone="blue"
|
||||
<MetricChartCard title={i18n.t('CPU 使用率(%)')} value={currentVal('cpu')} icon={<Cpu />} iconTone="blue"
|
||||
data={metrics.cpu} color="#3b82f6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s79f80fbf')} value={latest?.cpu_iowait != null ? `${(latest.cpu_iowait as number).toFixed(1)}%` : '--'} icon={<Clock />} iconTone="orange"
|
||||
<MetricChartCard title={i18n.t('CPU IO等待(%)')} value={latest?.cpu_iowait != null ? `${(latest.cpu_iowait as number).toFixed(1)}%` : '--'} icon={<Clock />} iconTone="orange"
|
||||
data={metrics.iowait} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s78262b59')} value={currentVal('mem')} icon={<Server />} iconTone="green"
|
||||
<MetricChartCard title={i18n.t('内存使用率(%)')} value={currentVal('mem')} icon={<Server />} iconTone="green"
|
||||
data={metrics.mem} color="#10b981" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.sa23fcda0')} value={latest?.mem_available != null ? `${(latest.mem_available as number).toFixed(1)}GB` : '--'} icon={<Server />} iconTone="green"
|
||||
<MetricChartCard title={i18n.t('可用内存(GB)')} value={latest?.mem_available != null ? `${(latest.mem_available as number).toFixed(1)}GB` : '--'} icon={<Server />} iconTone="green"
|
||||
data={metrics.memAvail} color="#34d399" unit="GB" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s05589bfd')} value={currentVal('swap')} icon={<HardDrive />} iconTone="red"
|
||||
<MetricChartCard title={i18n.t('Swap 使用率(%)')} value={currentVal('swap')} icon={<HardDrive />} iconTone="red"
|
||||
data={metrics.swap} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.sae62efb2')} value={currentVal('load1')} icon={<Activity />} iconTone="purple"
|
||||
<MetricChartCard title={i18n.t('系统负载(1m)')} value={currentVal('load1')} icon={<Activity />} iconTone="purple"
|
||||
data={metrics.load1} color="#8b5cf6" unit="" domain={[0, 'auto']} aggType={aggType} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ export function ContextSwitchCard({ csInfo }: { csInfo: JsonData }) {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]"> {i18n.t('monitor.hostmonitortsx.sae1a30b7')} </div>
|
||||
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]"> {i18n.t('无限制')} </div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -27,15 +27,15 @@ export function DiskTab({ metrics, latest, currentVal, aggType, pctRefLines, dis
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.sdf969221')} value={currentVal('disk')} icon={<HardDrive />} iconTone="orange"
|
||||
<MetricChartCard title={i18n.t('磁盘使用率(%)')} value={currentVal('disk')} icon={<HardDrive />} iconTone="orange"
|
||||
data={metrics.disk} color="#f97316" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s20296121')} value={inodeVal} icon={<HardDrive />} iconTone="purple"
|
||||
<MetricChartCard title={i18n.t('Swap 使用率(%)')} value={inodeVal} icon={<HardDrive />} iconTone="purple"
|
||||
data={metrics.inode} color="#8b5cf6" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s72e3c273')} value={currentVal('ioR')} icon={<Disc />} iconTone="blue"
|
||||
<MetricChartCard title={i18n.t('磁盘读取(KB/s)')} value={currentVal('ioR')} icon={<Disc />} iconTone="blue"
|
||||
data={metrics.ioRead} color="#3b82f6" unit="KB/s" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.sa9b96f9b')} value={currentVal('ioW')} icon={<Disc />} iconTone="green"
|
||||
<MetricChartCard title={i18n.t('磁盘写入(KB/s)')} value={currentVal('ioW')} icon={<Disc />} iconTone="green"
|
||||
data={metrics.ioWrite} color="#10b981" unit="KB/s" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s996adabd')} value={diskIOInfo?.util != null ? `${diskIOInfo.util.toFixed(1)}%` : '--'} icon={<Disc />} iconTone="red"
|
||||
<MetricChartCard title={i18n.t('磁盘IO繁忙度(%)')} value={diskIOInfo?.util != null ? `${diskIOInfo.util.toFixed(1)}%` : '--'} icon={<Disc />} iconTone="red"
|
||||
data={metrics.ioUtil} color="#ef4444" unit="%" domain={[0, 100]} aggType={aggType} referenceLines={pctRefLines} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,7 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--dg-text-muted)]"> {i18n.t('monitor.metricCatalog.colMax')} </span>
|
||||
<span className="font-mono text-[var(--dg-text-primary)] font-semibold">{fdInfo.max === -1 ? i18n.t('monitor.hostmonitortsx.sbc436447') : fdInfo.max?.toLocaleString() || '—'}</span>
|
||||
<span className="font-mono text-[var(--dg-text-primary)] font-semibold">{fdInfo.max === -1 ? i18n.t('无限制') : fdInfo.max?.toLocaleString() || '—'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--dg-text-muted)]"> {i18n.t('server.usage')} </span>
|
||||
@@ -57,11 +57,11 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo
|
||||
<ZoomableAreaChart
|
||||
ref={fdChartRef}
|
||||
data={fdMetrics.map((d) => ({ ...d, value: d[aggType] }))}
|
||||
series={[{ dataKey: 'value', color: '#a78bfa', label: i18n.t('monitor.hostmonitortsx.s28130dec') }]}
|
||||
series={[{ dataKey: 'value', color: '#a78bfa', label: i18n.t('FD 使用率') }]}
|
||||
yDomain={[0, 'auto']}
|
||||
unit="%"
|
||||
chartId="fd-usage"
|
||||
title={i18n.t('monitor.hostmonitortsx.sc026a1ea')}
|
||||
title={i18n.t('网卡带宽吞吐')}
|
||||
subtitle={fdInfo?.usage_pct != null ? `当前 ${fdInfo.usage_pct.toFixed(1)}%` : undefined}
|
||||
height={160}
|
||||
toolbarPlacement="header"
|
||||
@@ -71,7 +71,7 @@ export function FDUsageCard({ fdInfo, fdMetrics, aggType, fdChartRef, fdChartZoo
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]"> {i18n.t('monitor.hostmonitortsx.sae1a30b7')} </div>
|
||||
<div className="text-center py-4 text-xs text-[var(--dg-text-muted)]"> {i18n.t('无限制')} </div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -30,20 +30,20 @@ export function NetworkTab({ metrics, netInfo, currentVal, aggType }: NetworkTab
|
||||
data={metrics.tcpTW} color="#f59e0b" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title="TCP CLOSE_WAIT" value={netInfo?.tcp_close_wait != null ? String(netInfo.tcp_close_wait) : '--'} icon={<Clock />} iconTone="red"
|
||||
data={metrics.tcpCW} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s56c1b42a')} value={connTotal} icon={<Network />} iconTone="cyan"
|
||||
<MetricChartCard title={i18n.t('内存使用率(%)')} value={connTotal} icon={<Network />} iconTone="cyan"
|
||||
data={metrics.connTotal} color="#22d3ee" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
</div>
|
||||
<DualLineChart
|
||||
title={i18n.t('monitor.hostmonitortsx.s1e2a9e41')}
|
||||
title={i18n.t('主机监控')}
|
||||
currentRx={netInfo?.net_rx_bps != null ? formatBPS(netInfo.net_rx_bps) : '--'}
|
||||
currentTx={netInfo?.net_tx_bps != null ? formatBPS(netInfo.net_tx_bps) : '--'}
|
||||
rxData={metrics.rx} txData={metrics.tx}
|
||||
aggType={aggType} unit="KB/s"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-3">
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.s9f7e9902')} value={netInfo?.net_rx_errors != null ? String(netInfo.net_rx_errors) : '--'} icon={<XCircle />} iconTone="red"
|
||||
<MetricChartCard title={i18n.t('网卡接收错误')} value={netInfo?.net_rx_errors != null ? String(netInfo.net_rx_errors) : '--'} icon={<XCircle />} iconTone="red"
|
||||
data={metrics.rxErr} color="#ef4444" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
<MetricChartCard title={i18n.t('monitor.hostmonitortsx.sb2ffd58e')} value={netInfo?.net_tx_errors != null ? String(netInfo.net_tx_errors) : '--'} icon={<XCircle />} iconTone="orange"
|
||||
<MetricChartCard title={i18n.t('网卡发送错误')} value={netInfo?.net_tx_errors != null ? String(netInfo.net_tx_errors) : '--'} icon={<XCircle />} iconTone="orange"
|
||||
data={metrics.txErr} color="#f97316" unit="个" domain={[0, 'auto']} aggType={aggType} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function PortMonitorTab({ portInfo, portPolicyMode, portWhitelist, onOpen
|
||||
<tr className="text-[var(--dg-text-muted)] border-b border-[var(--dg-border)]">
|
||||
<th className="py-1.5 text-left font-medium"> {i18n.t('system.ldapPort')} </th>
|
||||
<th className="py-1.5 text-left font-medium"> {i18n.t('kubernetes.service.protocol')} </th>
|
||||
<th className="py-1.5 text-left font-medium"> {i18n.t('monitor.hostmonitortsx.sf88522cf')} </th>
|
||||
<th className="py-1.5 text-left font-medium"> {i18n.t('进程监控')} </th>
|
||||
<th className="py-1.5 text-right font-medium"> {i18n.t('system.terminalStatus')} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -20,11 +20,11 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="text-[var(--dg-text-muted)] border-b border-[var(--dg-border)]">
|
||||
<th className="py-1.5 text-left font-medium w-[40%]"> {i18n.t('monitor.hostmonitortsx.s23ecaf10')} </th>
|
||||
<th className="py-1.5 text-left font-medium w-[40%]"> {i18n.t('进程监控')} </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%]"> {i18n.t('monitor.hostmonitortsx.s78262b59')} </th>
|
||||
<th className="py-1.5 text-right font-medium w-[16%]"> {i18n.t('进程监控')} </th>
|
||||
<th className="py-1.5 text-right font-medium w-[16%]">CPU使用率(%)</th>
|
||||
<th className="py-1.5 text-right font-medium w-[20%]"> {i18n.t('monitor.hostmonitortsx.s8fe5ed35')} </th>
|
||||
<th className="py-1.5 text-right font-medium w-[20%]"> {i18n.t('进程监控')} </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -41,7 +41,7 @@ export function ProcessTable({ processTop }: { processTop: JsonData[] }) {
|
||||
</span>
|
||||
<button
|
||||
className="opacity-0 group-hover/cmd:opacity-100 transition-opacity p-0.5 hover:dg-text-cyan shrink-0"
|
||||
title={i18n.t('monitor.hostmonitortsx.s92cb88ae')}
|
||||
title={i18n.t('FD 文件句柄使用率')}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
|
||||
@@ -4,11 +4,11 @@ import i18n from '@/i18n';
|
||||
/** 预设时间范围(分钟粒度,支持 15/30 分钟) // Logan.Li */
|
||||
export const TIME_RANGE_PRESETS = [
|
||||
{ id: '15m', label: i18n.t('monitor.traceMgr.min15'), minutes: 15, buckets: 30 },
|
||||
{ id: '30m', label: i18n.t('monitor.typests.s751a79af'), minutes: 30, buckets: 36 },
|
||||
{ id: '30m', label: i18n.t('系统信息'), minutes: 30, buckets: 36 },
|
||||
{ id: '1h', label: i18n.t('monitor.traceMgr.hour1'), minutes: 60, buckets: 60 },
|
||||
{ id: '6h', label: i18n.t('monitor.traceMgr.hour6'), minutes: 360, buckets: 72 },
|
||||
{ id: '12h', label: i18n.t('monitor.typests.sd04708fe'), minutes: 720, buckets: 72 },
|
||||
{ id: '1d', label: i18n.t('monitor.typests.s7db888de'), minutes: 1440, buckets: 96 },
|
||||
{ id: '12h', label: i18n.t('系统信息'), minutes: 720, buckets: 72 },
|
||||
{ id: '1d', label: i18n.t('系统信息'), minutes: 1440, buckets: 96 },
|
||||
{ id: '7d', label: i18n.t('monitor.traceMgr.day7'), minutes: 10080, buckets: 84 },
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export function MiddlewareDialog({ open, onOpenChange, editTarget, onSuccess }:
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!form.name.trim() || !form.ip.trim()) {
|
||||
toast.error(i18n.t('monitor.middlewaredialo.s0fa2360e'));
|
||||
toast.error(i18n.t('请填写名称和 IP'));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
@@ -63,7 +63,7 @@ export function MiddlewareDialog({ open, onOpenChange, editTarget, onSuccess }:
|
||||
}
|
||||
if (editTarget) {
|
||||
await middlewareMonitorApi.update(editTarget.id, payload);
|
||||
toast.success(i18n.t('monitor.middlewaredialo.s55aa6366'));
|
||||
toast.success(i18n.t('更新成功'));
|
||||
} else {
|
||||
await middlewareMonitorApi.create(payload);
|
||||
toast.success(i18n.t('monitor.dashboard.custom.toastCreateSuccess'));
|
||||
@@ -81,8 +81,8 @@ export function MiddlewareDialog({ open, onOpenChange, editTarget, onSuccess }:
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent showOverlay={false} className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editTarget ? i18n.t('monitor.middlewaredialo.sc38ddef8') : i18n.t('monitor.middlewaredialo.sa24241b6')}</DialogTitle>
|
||||
<DialogDescription> {i18n.t('monitor.middlewaredialo.s6ee71754')} </DialogDescription>
|
||||
<DialogTitle>{editTarget ? i18n.t('编辑中间件') : i18n.t('添加中间件')}</DialogTitle>
|
||||
<DialogDescription> {i18n.t('监控实例')} </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 max-h-[60vh] overflow-y-auto pr-1">
|
||||
<div className="space-y-2">
|
||||
@@ -100,7 +100,7 @@ export function MiddlewareDialog({ open, onOpenChange, editTarget, onSuccess }:
|
||||
<div className="space-y-2">
|
||||
<Label> {i18n.t('deploy.form.deployMode')} </Label>
|
||||
<div className="flex gap-2">
|
||||
{[{ value: 'standalone' as const, label: i18n.t('monitor.helpersts.s047499fc') }, { value: 'cluster' as const, label: i18n.t('k8s.topology.typeCluster') }].map((opt) => (
|
||||
{[{ value: 'standalone' as const, label: i18n.t('单机') }, { value: 'cluster' as const, label: i18n.t('k8s.topology.typeCluster') }].map((opt) => (
|
||||
<button key={opt.value} type="button" onClick={() => setForm((f) => ({ ...f, redis_mode: opt.value }))}
|
||||
className={`flex-1 py-2 rounded-lg text-sm ${form.redis_mode === opt.value ? 'bg-blue-600 text-white' : 'bg-[var(--dg-bg-tertiary)]'}`}>
|
||||
{opt.label}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function MwOverviewCharts({ targets }: Props) {
|
||||
|
||||
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}>
|
||||
<OverviewChartCard title={i18n.t('实例状态')} 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>
|
||||
@@ -67,13 +67,13 @@ export function MwOverviewCharts({ targets }: Props) {
|
||||
)}
|
||||
</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}>
|
||||
<OverviewChartCard title={i18n.t('在线')} 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')} />} />
|
||||
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtHitRate(v / 100)} nameFormatter={() => i18n.t('命中率')} />} />
|
||||
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
|
||||
{redisHitData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
|
||||
</Bar>
|
||||
@@ -103,7 +103,7 @@ export function MwOverviewCharts({ targets }: Props) {
|
||||
<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')} />} />
|
||||
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtVal(v)} nameFormatter={() => i18n.t('连接')} />} />
|
||||
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
|
||||
{mysqlConnData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
|
||||
</Bar>
|
||||
|
||||
@@ -92,7 +92,7 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowAll((v) => !v)}>
|
||||
{showAll ? i18n.t('monitor.mwtargetchartsp.s9e75621e') : i18n.t('monitor.mwtargetchartsp.s9dea79ec')}
|
||||
{showAll ? i18n.t('核心指标') : i18n.t('全部指标')}
|
||||
</Button>
|
||||
<Select value={timeRange} onValueChange={(v) => setTimeRange(v as MwTimeRange)}>
|
||||
<SelectTrigger className="h-7 w-[92px] text-xs"><SelectValue /></SelectTrigger>
|
||||
@@ -116,17 +116,17 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
|
||||
{accent === 'redis' && (
|
||||
<>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('monitor.mwtargetchartsp.s6787355b')}
|
||||
label={i18n.t('命中率')}
|
||||
value={(m.redis_hit_rate || 0) * 100}
|
||||
display={fmtHitRate(m.redis_hit_rate)}
|
||||
level={m.redis_hit_rate > 0 ? gaugeToneLow((m.redis_hit_rate || 0) * 100, 90, 80) : 'ok'}
|
||||
/>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('monitor.mwtargetchartsp.s44cdb3f7')}
|
||||
label={i18n.t('命中率')}
|
||||
value={memPct ?? (m.redis_used_memory > 0 ? 50 : 0)}
|
||||
display={fmtMemory(m.redis_used_memory)}
|
||||
level={memPct != null ? gaugeTone(memPct, 80, 90) : 'ok'}
|
||||
hint={memPct != null ? `已用 ${memPct.toFixed(0)}%` : i18n.t('monitor.mwtargetchartsp.s1c1ce805')}
|
||||
hint={memPct != null ? `已用 ${memPct.toFixed(0)}%` : i18n.t('未配置 maxmemory')}
|
||||
/>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('kubernetes.topology.connections')}
|
||||
@@ -140,19 +140,19 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
|
||||
{accent === 'mysql' && (
|
||||
<>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('monitor.mwtargetchartsp.sa2d02e56')}
|
||||
label={i18n.t('命中率')}
|
||||
value={(m.mysql_buffer_hit_rate || 0) * 100}
|
||||
display={fmtHitRate(m.mysql_buffer_hit_rate)}
|
||||
level={m.mysql_buffer_hit_rate > 0 ? gaugeToneLow((m.mysql_buffer_hit_rate || 0) * 100, 95, 90) : 'ok'}
|
||||
/>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('monitor.mwtargetchartsp.se84e4bc5')}
|
||||
label={i18n.t('命中率')}
|
||||
value={connPct ?? 0}
|
||||
display={`${fmtVal(m.mysql_connections)} / ${fmtVal(m.mysql_max_connections)}`}
|
||||
level={connPct != null ? gaugeTone(connPct, 80, 90) : 'ok'}
|
||||
/>
|
||||
<MwGaugeBar
|
||||
label={i18n.t('monitor.mwtargetchartsp.se0f6abb7')}
|
||||
label={i18n.t('命中率')}
|
||||
value={Math.min(100, (m.mysql_threads_running || 0) * 5)}
|
||||
display={fmtVal(m.mysql_threads_running)}
|
||||
level={(m.mysql_threads_running || 0) > 20 ? 'warn' : 'ok'}
|
||||
@@ -167,15 +167,15 @@ export function MwTargetChartsPanel({ target, chartDefs, accent }: Props) {
|
||||
{accent === 'redis' ? (
|
||||
<>
|
||||
<SummaryTile label="QPS" value={fmtVal(m.redis_qps, 1)} />
|
||||
<SummaryTile label={i18n.t('monitor.mwtargetchartsp.s196eef1f')} value={fmtVal(m.redis_db_size)} />
|
||||
<SummaryTile label={i18n.t('monitor.mwtargetchartsp.s0a5e7a61')} value={fmtVal(m.redis_evicted_keys)} warn={(m.redis_evicted_keys || 0) > 0} />
|
||||
<SummaryTile label={i18n.t('monitor.mwtargetchartsp.s3bc8dd1f')} value={m.redis_mem_fragmentation_ratio ? fmtVal(m.redis_mem_fragmentation_ratio, 2) : '--'} />
|
||||
<SummaryTile label={i18n.t('碎片率')} value={fmtVal(m.redis_db_size)} />
|
||||
<SummaryTile label={i18n.t('碎片率')} value={fmtVal(m.redis_evicted_keys)} warn={(m.redis_evicted_keys || 0) > 0} />
|
||||
<SummaryTile label={i18n.t('逐出 Key')} value={m.redis_mem_fragmentation_ratio ? fmtVal(m.redis_mem_fragmentation_ratio, 2) : '--'} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SummaryTile label="QPS" value={fmtVal(m.mysql_qps, 1)} />
|
||||
<SummaryTile label={i18n.t('monitor.mwtargetchartsp.s3e54c81c')} value={fmtMemory(m.mysql_bytes_received)} />
|
||||
<SummaryTile label={i18n.t('monitor.mwtargetchartsp.s90d6afb7')} value={fmtMemory(m.mysql_bytes_sent)} />
|
||||
<SummaryTile label={i18n.t('发送流量')} value={fmtMemory(m.mysql_bytes_received)} />
|
||||
<SummaryTile label={i18n.t('发送流量')} value={fmtMemory(m.mysql_bytes_sent)} />
|
||||
<SummaryTile label={i18n.t('agent.agentUptime')} value={m.mysql_uptime ? `${Math.floor(m.mysql_uptime / 3600)}h` : '--'} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -59,20 +59,20 @@ export const MYSQL_PRIMARY_KEYS = ['mysql_qps', 'mysql_connections', 'mysql_buff
|
||||
|
||||
export const REDIS_CHARTS: MwChartDef[] = [
|
||||
{ key: 'redis_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'ops/s' },
|
||||
{ key: 'redis_used_memory', label: i18n.t('monitor.mwtargetchartsp.s44cdb3f7'), color: 'var(--dg-red)', formatter: fmtMemory },
|
||||
{ key: 'redis_hit_rate', label: i18n.t('monitor.mwtargetchartsp.s6787355b'), color: 'var(--dg-green)', percent: true },
|
||||
{ key: 'redis_used_memory', label: i18n.t('内存占用'), color: 'var(--dg-red)', formatter: fmtMemory },
|
||||
{ key: 'redis_hit_rate', label: i18n.t('命中率'), color: 'var(--dg-green)', percent: true },
|
||||
{ key: 'redis_connected_clients', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' },
|
||||
{ key: 'redis_db_size', label: i18n.t('monitor.helpersts.sc5f65eec'), color: 'var(--dg-purple)' },
|
||||
{ key: 'redis_blocked_clients', label: i18n.t('monitor.helpersts.s2ad27e85'), color: 'var(--dg-orange)' },
|
||||
{ key: 'redis_db_size', label: i18n.t('慢查询速率'), color: 'var(--dg-purple)' },
|
||||
{ key: 'redis_blocked_clients', label: i18n.t('阻塞客户端'), color: 'var(--dg-orange)' },
|
||||
];
|
||||
|
||||
export const MYSQL_CHARTS: MwChartDef[] = [
|
||||
{ key: 'mysql_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'q/s' },
|
||||
{ key: 'mysql_connections', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' },
|
||||
{ key: 'mysql_threads_running', label: i18n.t('monitor.mwtargetchartsp.se0f6abb7'), color: 'var(--dg-purple)' },
|
||||
{ key: 'mysql_buffer_hit_rate', label: i18n.t('monitor.mwtargetchartsp.sa2d02e56'), color: 'var(--dg-green)', percent: true },
|
||||
{ key: 'mysql_slow_queries', label: i18n.t('monitor.helpersts.sbd73bca3'), color: 'var(--dg-orange)', unit: i18n.t('monitor.helpersts.s3878fe68'), counterDelta: true },
|
||||
{ key: 'mysql_bytes_sent', label: i18n.t('monitor.mwtargetchartsp.s90d6afb7'), color: 'var(--dg-amber)', formatter: fmtMemory },
|
||||
{ key: 'mysql_threads_running', label: i18n.t('活跃线程'), color: 'var(--dg-purple)' },
|
||||
{ key: 'mysql_buffer_hit_rate', label: i18n.t('缓冲池命中率'), color: 'var(--dg-green)', percent: true },
|
||||
{ key: 'mysql_slow_queries', label: i18n.t('慢查询速率'), color: 'var(--dg-orange)', unit: i18n.t('次/周期'), counterDelta: true },
|
||||
{ key: 'mysql_bytes_sent', label: i18n.t('缓冲池命中率'), color: 'var(--dg-amber)', formatter: fmtMemory },
|
||||
];
|
||||
|
||||
export function fmtMemory(bytes: number): string {
|
||||
@@ -139,7 +139,7 @@ export function statusBg(status: string): string {
|
||||
}
|
||||
|
||||
export function redisModeLabel(mode?: string): string {
|
||||
return mode === 'cluster' ? i18n.t('k8s.topology.typeCluster') : i18n.t('monitor.helpersts.s047499fc');
|
||||
return mode === 'cluster' ? i18n.t('k8s.topology.typeCluster') : i18n.t('集群');
|
||||
}
|
||||
|
||||
export function redisMemoryPct(metrics: Record<string, number>): number | null {
|
||||
@@ -209,27 +209,27 @@ export function evalMwHealth(target: MwTarget): MwHealthIssue[] {
|
||||
const m = target.latest_metrics || {};
|
||||
const issues: MwHealthIssue[] = [];
|
||||
if (target.status !== 'online') {
|
||||
issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s4ace6502') });
|
||||
issues.push({ level: 'crit', message: i18n.t('实例离线') });
|
||||
return issues;
|
||||
}
|
||||
if (target.description === 'redis') {
|
||||
if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.8) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.sff1d979b') });
|
||||
else if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.9) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s11902306') });
|
||||
if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.8) issues.push({ level: 'crit', message: i18n.t('命中率 < 80%') });
|
||||
else if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.9) issues.push({ level: 'warn', message: i18n.t('命中率 < 90%') });
|
||||
const memPct = redisMemoryPct(m);
|
||||
if (memPct != null && memPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s07eee3b9') });
|
||||
else if (memPct != null && memPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.sbb098add') });
|
||||
if (m.redis_blocked_clients > 0) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s2ad27e85') });
|
||||
if (m.redis_evicted_keys > 0) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.sc4db73a4') });
|
||||
if (memPct != null && memPct > 90) issues.push({ level: 'crit', message: i18n.t('内存 > 90%') });
|
||||
else if (memPct != null && memPct > 80) issues.push({ level: 'warn', message: i18n.t('内存 > 80%') });
|
||||
if (m.redis_blocked_clients > 0) issues.push({ level: 'warn', message: i18n.t('阻塞客户端') });
|
||||
if (m.redis_evicted_keys > 0) issues.push({ level: 'warn', message: i18n.t('Key 逐出') });
|
||||
} else if (target.description === 'mysql') {
|
||||
if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.9) {
|
||||
issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s99844184') });
|
||||
issues.push({ level: 'crit', message: i18n.t('缓冲池命中 < 90%') });
|
||||
} else if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.95) {
|
||||
issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s41ec17d6') });
|
||||
issues.push({ level: 'warn', message: i18n.t('缓冲池命中 < 95%') });
|
||||
}
|
||||
const connPct = mysqlConnPct(m);
|
||||
if (connPct != null && connPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s5b95d546') });
|
||||
else if (connPct != null && connPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.se0b952ab') });
|
||||
if (m.mysql_threads_running > 20) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s56174d14') });
|
||||
if (connPct != null && connPct > 90) issues.push({ level: 'crit', message: i18n.t('连接 > 90% 上限') });
|
||||
else if (connPct != null && connPct > 80) issues.push({ level: 'warn', message: i18n.t('连接 > 80% 上限') });
|
||||
if (m.mysql_threads_running > 20) issues.push({ level: 'warn', message: i18n.t('活跃线程偏多') });
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function SiteProtocolPieChart({ tasks }: Props) {
|
||||
if (tasks.length === 0) {
|
||||
return (
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-6 text-center text-sm text-[var(--dg-text-muted)]"> {i18n.t('monitor.siteprotocolpie.s0589e591')} </CardContent>
|
||||
<CardContent className="p-6 text-center text-sm text-[var(--dg-text-muted)]"> {i18n.t('可用率(24h)')} </CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export function SiteProtocolPieChart({ tasks }: Props) {
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<PieChartIcon className="h-4 w-4 text-[var(--dg-text-muted)]" />
|
||||
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]"> {i18n.t('monitor.siteprotocolpie.s20721c88')} </h3>
|
||||
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]"> {i18n.t('可用率(24h)')} </h3>
|
||||
<span className="text-[10px] text-[var(--dg-text-muted)]">共 {tasks.length} 个任务</span>
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
|
||||
@@ -77,11 +77,11 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
setProbing(true);
|
||||
try {
|
||||
await siteApi.probeNow(task.id);
|
||||
toast.success(i18n.t('monitor.siteresultspane.sb8649f2b'));
|
||||
toast.success(i18n.t('拨测完成'));
|
||||
loadResults();
|
||||
onProbed();
|
||||
} catch (e) {
|
||||
toast.error((e as { message?: string }).message || i18n.t('monitor.siteresultspane.s3f60d8f1'));
|
||||
toast.error((e as { message?: string }).message || i18n.t('拨测失败'));
|
||||
} finally {
|
||||
setProbing(false);
|
||||
}
|
||||
@@ -128,8 +128,8 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
return (
|
||||
<TableRow className="border-[var(--dg-border)] hover:bg-transparent">
|
||||
<TableHead className="text-xs"> {i18n.t('system.auditTime')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteresultspane.s3f04bb50')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteresultspane.s499b9a62')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('编辑拨测任务')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('编辑拨测任务')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteProtocol.ssl.daysName')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteProtocol.ssl.issuer')} </TableHead>
|
||||
</TableRow>
|
||||
@@ -140,7 +140,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<TableHead className="text-xs"> {i18n.t('system.auditTime')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteProtocol.tcp.connected')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteProtocol.latency.latencyName')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteresultspane.sa7d1436b')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('编辑拨测任务')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('cloud.colDescription')} </TableHead>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -150,7 +150,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<TableHead className="text-xs"> {i18n.t('system.auditTime')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('system.terminalStatus')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteProtocol.latency.latencyName')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('monitor.siteresultspane.s3197ed7a')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('编辑拨测任务')} </TableHead>
|
||||
<TableHead className="text-xs"> {i18n.t('cloud.colDescription')} </TableHead>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -223,10 +223,10 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<Select value={timeRange} onValueChange={(v) => setTimeRange(v as SiteTimeRange)}>
|
||||
<SelectTrigger className="h-7 w-[92px] text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1h"> {i18n.t('monitor.helpersts.sf972b7b1')} </SelectItem>
|
||||
<SelectItem value="1h"> {i18n.t('探测次数')} </SelectItem>
|
||||
<SelectItem value="6h"> {i18n.t('monitor.siteProtocol.rangeDefault')} </SelectItem>
|
||||
<SelectItem value="24h"> {i18n.t('monitor.helpersts.s6e99b786')} </SelectItem>
|
||||
<SelectItem value="7d"> {i18n.t('monitor.helpersts.sd765efc4')} </SelectItem>
|
||||
<SelectItem value="24h"> {i18n.t('探测次数')} </SelectItem>
|
||||
<SelectItem value="7d"> {i18n.t('探测次数')} </SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadResults} disabled={loading}>
|
||||
@@ -259,7 +259,7 @@ export function SiteResultsPanel({ task, onProbed, fullPage = false }: Props) {
|
||||
<SiteProtocolObs task={task} results={results} rangeLabel={rangeLabel} />
|
||||
<div className={`mt-4 overflow-x-auto ${fullPage ? 'max-h-80' : 'max-h-56'}`}>
|
||||
{results.length === 0 ? (
|
||||
<p className="text-center py-6 text-sm text-[var(--dg-text-muted)]"> {i18n.t('monitor.siteresultspane.s592b56ff')} </p>
|
||||
<p className="text-center py-6 text-sm text-[var(--dg-text-muted)]"> {i18n.t('可用率(24h)')} </p>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>{renderTableHead()}</TableHeader>
|
||||
|
||||
@@ -57,10 +57,10 @@ export const SITE_TIME_RANGE_HOURS: Record<SiteTimeRange, number> = {
|
||||
};
|
||||
|
||||
export const SITE_TIME_RANGE_LABELS: Record<SiteTimeRange, string> = {
|
||||
'1h': i18n.t('monitor.helpersts.sf972b7b1'),
|
||||
'1h': i18n.t('近 1 小时'),
|
||||
'6h': i18n.t('monitor.siteProtocol.rangeDefault'),
|
||||
'24h': i18n.t('monitor.helpersts.s6e99b786'),
|
||||
'7d': i18n.t('monitor.helpersts.sd765efc4'),
|
||||
'24h': i18n.t('近 24 小时'),
|
||||
'7d': i18n.t('近 7 天'),
|
||||
};
|
||||
|
||||
/** 各协议推荐默认拨测间隔(秒)// Logan.Li */
|
||||
@@ -86,11 +86,11 @@ export function defaultProbeFrequency(type: ProbeType): number {
|
||||
}
|
||||
|
||||
export const probeTypeMeta: Record<ProbeType, { label: string; placeholder: string; hint: string }> = {
|
||||
HTTP: { label: i18n.t('monitor.helpersts.s277c74b4'), placeholder: 'https://example.com/health', hint: i18n.t('monitor.helpersts.s953f0b93') },
|
||||
SSL: { label: i18n.t('monitor.helpersts.s8e3a1ea5'), placeholder: i18n.t('monitor.helpersts.s8a6cb9ae'), hint: i18n.t('monitor.helpersts.sefe596b0') },
|
||||
TCP: { label: i18n.t('monitor.helpersts.seeda3abc'), placeholder: i18n.t('monitor.helpersts.sd315f501'), hint: i18n.t('monitor.helpersts.s5a87d291') },
|
||||
HTTP: { label: i18n.t('目标 URL'), placeholder: 'https://example.com/health', hint: i18n.t('Web 请求(支持 http/https),检测状态码与响应时间') },
|
||||
SSL: { label: i18n.t('SSL 地址'), placeholder: i18n.t('example.com:443 或 https://example.com'), hint: i18n.t('TLS 握手检测证书(支持 host:port / https:// 域名,不发起 HTTP 请求)') },
|
||||
TCP: { label: i18n.t('主机:端口'), placeholder: i18n.t('192.168.1.1:8080 或 example.com:443'), hint: i18n.t('TCP 端口连通性') },
|
||||
DNS: { label: i18n.t('cloud.domainName'), placeholder: 'example.com', hint: i18n.t('monitor.helpersts.sd3fd8f1d') },
|
||||
PING: { label: i18n.t('monitor.helpersts.saeb5271e'), placeholder: i18n.t('monitor.helpersts.sd6bbd624'), hint: i18n.t('monitor.helpersts.se850c8e9') },
|
||||
PING: { label: i18n.t('主机地址'), placeholder: i18n.t('192.168.1.1 或 example.com'), hint: i18n.t('ICMP 连通性与延迟(默认 2 分钟/次)') },
|
||||
};
|
||||
|
||||
export function probeFrequencyHint(type: ProbeType): string {
|
||||
|
||||
@@ -93,16 +93,16 @@ export function analyzeHttpResults(results: SiteResult[], task: SiteTask): HttpO
|
||||
}
|
||||
if (code >= 500) {
|
||||
err5xx++;
|
||||
pushEvent(events, r, `HTTP ${code}`, 'error', r.error_message || i18n.t('monitor.analyticsts.s71de8af8'));
|
||||
pushEvent(events, r, `HTTP ${code}`, 'error', r.error_message || i18n.t('服务端错误'));
|
||||
} else if (code >= 400) {
|
||||
err4xx++;
|
||||
pushEvent(events, r, `HTTP ${code}`, 'warn', r.error_message || i18n.t('monitor.analyticsts.sdc751cd0'));
|
||||
pushEvent(events, r, `HTTP ${code}`, 'warn', r.error_message || i18n.t('客户端/鉴权错误'));
|
||||
} else if (r.status === 'timeout') {
|
||||
otherErr++;
|
||||
pushEvent(events, r, i18n.t('common.timeoutError'), 'error', r.error_message);
|
||||
} else {
|
||||
otherErr++;
|
||||
pushEvent(events, r, code > 0 ? `HTTP ${code}` : i18n.t('monitor.analyticsts.sf50bf418'), 'error', r.error_message);
|
||||
pushEvent(events, r, code > 0 ? `HTTP ${code}` : i18n.t('请求失败'), 'error', r.error_message);
|
||||
}
|
||||
}
|
||||
const latest = results[0];
|
||||
@@ -128,7 +128,7 @@ export function analyzeSslResults(results: SiteResult[], task: SiteTask): SslObs
|
||||
const events: ProbeEvent[] = [];
|
||||
for (const r of results) {
|
||||
if (r.status !== 'success') {
|
||||
pushEvent(events, r, i18n.t('monitor.analyticsts.sddbae5b7'), 'error', r.error_message);
|
||||
pushEvent(events, r, i18n.t('请求超时'), 'error', r.error_message);
|
||||
} else if (r.ssl_days_remaining != null && r.ssl_days_remaining < 30) {
|
||||
pushEvent(events, r, `证书剩余 ${r.ssl_days_remaining} 天`, r.ssl_days_remaining <= 7 ? 'error' : 'warn', expiry);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ export function analyzeTcpResults(results: SiteResult[]): ConnectivityObsSummary
|
||||
const failed = r.status !== 'success';
|
||||
if (failed) {
|
||||
lastFailAt = r.checked_at;
|
||||
pushEvent(events, r, i18n.t('monitor.analyticsts.s0614a3ce'), 'error', r.error_message || i18n.t('aiops.llmTestFailed'));
|
||||
pushEvent(events, r, i18n.t('连接失败'), 'error', r.error_message || i18n.t('aiops.llmTestFailed'));
|
||||
}
|
||||
return { time: fmtTime(r.checked_at).slice(-5), up: failed ? 0 : 100, failed };
|
||||
});
|
||||
|
||||
@@ -116,8 +116,8 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="6h"> {i18n.t('monitor.siteProtocol.rangeDefault')} </SelectItem>
|
||||
<SelectItem value="24h"> {i18n.t('monitor.helpersts.s6e99b786')} </SelectItem>
|
||||
<SelectItem value="7d"> {i18n.t('monitor.helpersts.sd765efc4')} </SelectItem>
|
||||
<SelectItem value="24h"> {i18n.t('选择模板')} </SelectItem>
|
||||
<SelectItem value="7d"> {i18n.t('选择模板')} </SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={loadCharts} disabled={loading}>
|
||||
@@ -129,9 +129,9 @@ export function SnmpDeviceChartsPanel({ device, templateName }: Props) {
|
||||
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
{[
|
||||
{ label: i18n.t('monitor.snmpdevicechart.s53a04150'), value: latestCpu ? fmtMetricValue(latestCpu.latest_value, latestCpu.unit || '%') : '--' },
|
||||
{ label: i18n.t('monitor.snmpdevicechart.s33962aa4'), value: latestMem ? fmtMetricValue(latestMem.latest_value, latestMem.unit || '%') : '--' },
|
||||
{ label: i18n.t('monitor.snmpdevicechart.s26bddc00'), value: i18n.t('monitor.snmpdevicechart.sfdef8c23') },
|
||||
{ label: i18n.t('设备总数'), value: latestCpu ? fmtMetricValue(latestCpu.latest_value, latestCpu.unit || '%') : '--' },
|
||||
{ label: i18n.t('设备总数'), value: latestMem ? fmtMetricValue(latestMem.latest_value, latestMem.unit || '%') : '--' },
|
||||
{ label: i18n.t('设备总数'), value: i18n.t('设备总数') },
|
||||
].map((item) => (
|
||||
<div key={item.label} className="rounded-lg bg-[var(--dg-bg-tertiary)] p-3 text-center">
|
||||
<p className="text-[10px] text-[var(--dg-text-muted)]">{item.label}</p>
|
||||
|
||||
Reference in new issue
Block a user