webui i18n: localize EKG panel labels and actions (zh/en)

This commit is contained in:
DBT
2026-03-01 10:45:54 +00:00
parent be2381f128
commit dbdf2e832a
2 changed files with 27 additions and 9 deletions

View File

@@ -20,6 +20,14 @@ const resources = {
sidebarOps: 'Operations',
sidebarInsights: 'Insights',
ekg: 'EKG',
ekgEscalations: 'Escalations',
ekgSourceStats: 'Source Stats',
ekgChannelStats: 'Channel Stats',
ekgTopProvidersWorkload: 'Top Providers (workload)',
ekgTopProvidersAll: 'Top Providers (all)',
ekgTopErrsigWorkload: 'Top Error Signatures (workload)',
ekgTopErrsigHeartbeat: 'Top Error Signatures (heartbeat)',
ekgTopErrsigAll: 'Top Error Signatures (all)',
taskList: 'Task List',
taskDetail: 'Task Detail',
taskQueue: 'Task Queue',
@@ -195,6 +203,14 @@ const resources = {
sidebarOps: '运维',
sidebarInsights: '洞察',
ekg: 'EKG',
ekgEscalations: '升级拦截次数',
ekgSourceStats: '来源统计',
ekgChannelStats: '通道统计',
ekgTopProvidersWorkload: 'Top Providers业务负载',
ekgTopProvidersAll: 'Top Providers全量',
ekgTopErrsigWorkload: 'Top 错误签名(业务负载)',
ekgTopErrsigHeartbeat: 'Top 错误签名(心跳)',
ekgTopErrsigAll: 'Top 错误签名(全量)',
taskList: '任务列表',
taskDetail: '任务详情',
taskQueue: '任务队列',

View File

@@ -1,9 +1,11 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
type EKGKV = { key?: string; score?: number; count?: number };
const EKG: React.FC = () => {
const { t } = useTranslation();
const { q } = useAppContext();
const [loading, setLoading] = useState(false);
const [ekgWindow, setEkgWindow] = useState<'6h' | '24h' | '7d'>(() => {
@@ -56,47 +58,47 @@ const EKG: React.FC = () => {
<option value="24h">24h</option>
<option value="7d">7d</option>
</select>
<button onClick={fetchData} className="px-3 py-1.5 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-sm">{loading ? 'Loading...' : 'Refresh'}</button>
<button onClick={fetchData} className="px-3 py-1.5 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-sm">{loading ? t('loading') : t('refresh')}</button>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-3 text-xs">
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3">
<div className="text-zinc-500 mb-1">Escalations</div>
<div className="text-zinc-500 mb-1">{t('ekgEscalations')}</div>
<div className="text-zinc-100 text-2xl font-semibold">{escalationCount}</div>
</div>
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3">
<div className="text-zinc-500 mb-1">Source Stats</div>
<div className="text-zinc-500 mb-1">{t('ekgSourceStats')}</div>
<div className="space-y-1">{Object.keys(sourceStats).length === 0 ? <div className="text-zinc-500">-</div> : Object.entries(sourceStats).map(([k,v]) => <div key={k} className="text-zinc-200">{k}: <span className="text-zinc-400">{v}</span></div>)}</div>
</div>
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3">
<div className="text-zinc-500 mb-1">Channel Stats</div>
<div className="text-zinc-500 mb-1">{t('ekgChannelStats')}</div>
<div className="space-y-1">{Object.keys(channelStats).length === 0 ? <div className="text-zinc-500">-</div> : Object.entries(channelStats).map(([k,v]) => <div key={k} className="text-zinc-200">{k}: <span className="text-zinc-400">{v}</span></div>)}</div>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-3 text-xs">
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3">
<div className="text-zinc-500 mb-1">Top Providers (workload)</div>
<div className="text-zinc-500 mb-1">{t('ekgTopProvidersWorkload')}</div>
<div className="space-y-1">{providerTopWorkload.length === 0 ? <div className="text-zinc-500">-</div> : providerTopWorkload.map((x,i)=><div key={i} className="text-zinc-200">{x.key} <span className="text-zinc-500">({Number(x.score||0).toFixed(2)})</span></div>)}</div>
</div>
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3">
<div className="text-zinc-500 mb-1">Top Providers (all)</div>
<div className="text-zinc-500 mb-1">{t('ekgTopProvidersAll')}</div>
<div className="space-y-1">{providerTop.length === 0 ? <div className="text-zinc-500">-</div> : providerTop.map((x,i)=><div key={i} className="text-zinc-200">{x.key} <span className="text-zinc-500">({Number(x.score||0).toFixed(2)})</span></div>)}</div>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-3 text-xs flex-1 min-h-0">
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3 overflow-y-auto">
<div className="text-zinc-500 mb-1">Top Error Signatures (workload)</div>
<div className="text-zinc-500 mb-1">{t('ekgTopErrsigWorkload')}</div>
<div className="space-y-1">{errsigTopWorkload.length === 0 ? <div className="text-zinc-500">-</div> : errsigTopWorkload.map((x,i)=><div key={i} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count||0})</span></div>)}</div>
</div>
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3 overflow-y-auto">
<div className="text-zinc-500 mb-1">Top Error Signatures (heartbeat)</div>
<div className="text-zinc-500 mb-1">{t('ekgTopErrsigHeartbeat')}</div>
<div className="space-y-1">{errsigTopHeartbeat.length === 0 ? <div className="text-zinc-500">-</div> : errsigTopHeartbeat.map((x,i)=><div key={i} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count||0})</span></div>)}</div>
</div>
<div className="rounded-xl border border-zinc-800 bg-zinc-900/40 p-3 overflow-y-auto">
<div className="text-zinc-500 mb-1">Top Error Signatures (all)</div>
<div className="text-zinc-500 mb-1">{t('ekgTopErrsigAll')}</div>
<div className="space-y-1">{errsigTop.length === 0 ? <div className="text-zinc-500">-</div> : errsigTop.map((x,i)=><div key={i} className="text-zinc-200 truncate">{x.key} <span className="text-zinc-500">(x{x.count||0})</span></div>)}</div>
</div>
</div>