fix(i18n): U18 幻影 i18n hash 键治理 — 从 git 历史恢复 1540/1696 处原始中文文本 (91%)

问题: i18n 批量转换 (commit 55f216c) 生成了 813 个 hash 键引用但翻译从未写入 JSON,
导致用户在受影响模块看到 hash 键路径(如 monitor.quickquerybarts.s64559524)而非实际文本.
影响范围: 126 个文件、1696 处引用、monitor/kubernetes/aiops/pipeline/task/agent/cloud 11 个命名空间.

修复策略: 用 git 历史中 pre-i18n 版本(commit 7e1c8f5)的原始中文文本,
通过结构相似度匹配(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:
attacker committed 2026-07-11 05:49:23 +08:00
1 parent be1011eb58
commit 4c4aaf60f6
100 files changed
+688 -688

No files matched your search

@@ -131,7 +131,7 @@ export function AlertChannelDialog({
{/* 渠道列表 */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('monitor.alertchanneldia.se6ad00af')} </Label>
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('>渠道名称 <span className=')} </Label>
<Button
size="sm"
variant="outline"
@@ -207,10 +207,10 @@ export function AlertChannelDialog({
{/* 渠道表单 */}
<div className="space-y-4">
<Label className="text-[var(--dg-text-secondary)] text-sm">
{editingChannel ? i18n.t('monitor.alertchanneldia.sc4714508') : i18n.t('monitor.alertchanneldia.s82bd472e')}
{editingChannel ? i18n.t('编辑渠道') : i18n.t('新建渠道')}
</Label>
<div className="space-y-1.5">
<Label className="dg-text-secondary text-xs"> {i18n.t('monitor.alertchanneldia.sb8cbd86d')} <span className="dg-text-red">*</span></Label>
<Label className="dg-text-secondary text-xs"> {i18n.t('>渠道名称 <span className=')} <span className="dg-text-red">*</span></Label>
<Input
value={channelForm.name}
onChange={(e) => setChannelForm({ ...channelForm, name: e.target.value })}
@@ -76,15 +76,15 @@ export function AlertManager() {
];
const pageTabs = [
{ id: 'events', label: i18n.t('kubernetes.eventspagetsx.s0e5b83cc') },
{ id: 'events', label: i18n.t('告警事件') },
{ id: 'rules', label: i18n.t('monitor.alertRules') },
];
return (
<PageShell
variant="list"
title={i18n.t('monitor.alertmanagertsx.sa57c10a3')}
subtitle={i18n.t('monitor.alertmanagertsx.s1a70ca90')}
title={i18n.t('监控告警')}
subtitle={i18n.t('告警规则管理与告警事件处理')}
icon={<Bell className="h-5 w-5" />}
tabs={pageTabs}
activeTab={activeTab}
@@ -94,7 +94,7 @@ export function AlertManager() {
items={[
{ label: i18n.t('monitor.alertRules'), value: rules.length, color: 'blue' },
{ label: i18n.t('monitor.alertHelper.statusFiring'), value: alerts.filter((e) => e.status === 'firing').length, color: 'red' },
{ label: i18n.t('monitor.alertmanagertsx.s240597ec'), value: alerts.length, color: 'amber' },
{ label: i18n.t('今日告警'), value: alerts.length, color: 'amber' },
{ label: i18n.t('kubernetes.msg.uncordonSuccess'), value: alerts.filter((e) => e.status === 'resolved').length, color: 'emerald' },
]}
/>
@@ -128,10 +128,10 @@ export function AlertRuleDialog({
<DialogContent className="dg-input max-w-lg">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">
{editingRule ? i18n.t('monitor.alertruledialog.sb62c8bc1') : i18n.t('monitor.alertruledialog.s3564fa12')}
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
</DialogTitle>
<DialogDescription className="dg-text-muted">
{editingRule ? i18n.t('monitor.alertruledialog.s44a3bb2d') : i18n.t('monitor.alertruledialog.sb785eced')}
{editingRule ? i18n.t('编辑告警规则') : i18n.t('新建告警规则')}
</DialogDescription>
</DialogHeader>
@@ -141,7 +141,7 @@ export function AlertRuleDialog({
<Input
value={ruleForm.name}
onChange={(e) => setRuleForm({ ...ruleForm, name: e.target.value })}
placeholder={i18n.t('monitor.alertruledialog.sf111cd25')}
placeholder={i18n.t('如:CPU 使用率告警')}
className="dg-input"
/>
</div>
@@ -151,7 +151,7 @@ export function AlertRuleDialog({
<Textarea
value={ruleForm.description}
onChange={(e) => setRuleForm({ ...ruleForm, description: e.target.value })}
placeholder={i18n.t('monitor.alertruledialog.scc807bdb')}
placeholder={i18n.t('如:CPU 使用率告警')}
rows={2}
className="dg-input resize-none"
/>
@@ -159,7 +159,7 @@ export function AlertRuleDialog({
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.s36582565')} </Label>
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
<select
value={ruleForm.type}
onChange={(e) => {
@@ -169,9 +169,9 @@ export function AlertRuleDialog({
}}
className="w-full h-9 px-3 rounded-md bg-[var(--dg-bg-tertiary)]/50 border border-[var(--dg-border)] text-[var(--dg-text-primary)] text-sm"
>
<option value="metric"> {i18n.t('monitor.alertruledialog.s38546de8')} </option>
<option value="metric"> {i18n.t('>请先前往「日志库」添加 {ruleForm.source === ')} </option>
<option value="log"> {i18n.t('monitor.alertHelper.srcLog')} </option>
<option value="event"> {i18n.t('monitor.alertruledialog.s80069bea')} </option>
<option value="event"> {i18n.t('>请先前往「日志库」添加 {ruleForm.source === ')} </option>
</select>
</div>
<div className="space-y-1.5">
@@ -199,13 +199,13 @@ export function AlertRuleDialog({
{ruleForm.type === 'log' && (
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.sa2bc1edd')} <span className="dg-text-red">*</span></Label>
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} <span className="dg-text-red">*</span></Label>
<select
value={ruleForm.datasource_id || ''}
onChange={(e) => setRuleForm({ ...ruleForm, datasource_id: Number(e.target.value) || undefined })}
className="w-full h-9 px-3 rounded-md bg-[var(--dg-bg-tertiary)]/50 border border-[var(--dg-border)] text-[var(--dg-text-primary)] text-sm"
>
<option value=""> {i18n.t('monitor.alertruledialog.s9acb966d')} </option>
<option value=""> {i18n.t('PromQL 或指标表达式,例如 cpu_usage > 80')} </option>
{logSources.map((s) => (
<option key={s.id} value={s.id}>{s.name}{s.is_default ? i18n.t('monitor.logPanel.defaultSuffix') : ''}</option>
))}
@@ -217,7 +217,7 @@ export function AlertRuleDialog({
)}
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.s57ee9aea')} <span className="dg-text-red">*</span></Label>
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>规则名称 <span className=')} <span className="dg-text-red">*</span></Label>
<Input
value={ruleForm.expression}
onChange={(e) => setRuleForm({ ...ruleForm, expression: e.target.value })}
@@ -226,14 +226,14 @@ export function AlertRuleDialog({
/>
<p className="text-xs dg-text-muted">
{ruleForm.type === 'log'
? (ruleForm.source === 'loki' ? i18n.t('monitor.alertruledialog.s465c2bb1') : i18n.t('monitor.alertruledialog.s7072bc91'))
: i18n.t('monitor.alertruledialog.se2017efe')}
? (ruleForm.source === 'loki' ? i18n.t('LogQL 查询或 count_over_time 表达式') : i18n.t('Elasticsearch Lucene 查询'))
: i18n.t('PromQL 或指标表达式,例如 cpu_usage > 80')}
</p>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.sfce93b38')} </Label>
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
<select
value={ruleForm.severity}
onChange={(e) => setRuleForm({ ...ruleForm, severity: e.target.value })}
@@ -245,7 +245,7 @@ export function AlertRuleDialog({
</select>
</div>
<div className="space-y-1.5">
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('monitor.alertruledialog.scce774b3')} </Label>
<Label className="text-[var(--dg-text-secondary)]"> {i18n.t('>日志数据源 <span className=')} </Label>
<Input
type="number"
value={ruleForm.duration}
@@ -303,7 +303,7 @@ export function AlertRuleDialog({
onClick={handleSaveRule}
>
{ruleSaving && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('monitor.alertruledialog.s8e1e3dea')}
{editingRule ? i18n.t('cloud.saveChanges') : i18n.t('编辑告警规则')}
</Button>
</DialogFooter>
</DialogContent>
@@ -54,15 +54,15 @@ export function AlertRulesTab({
const handleDeleteRule = async ( id: number) => {
const ok = await confirm({
title: i18n.t('monitor.alertrulestabts.sfa5bfdc3'),
description: i18n.t('monitor.alertrulestabts.sd301558d'),
title: i18n.t('删除告警规则'),
description: i18n.t('确定删除此告警规则?'),
confirmText: i18n.t('kubernetes.storage.delete2'),
variant: 'destructive',
});
if (!ok) return;
try {
await alertApi.deleteRule(id);
toast.success(i18n.t('monitor.alertrulestabts.s20b3fdcd'));
toast.success(i18n.t('告警规则已删除'));
onReload();
} catch (error) {
console.error('Failed to delete rule:', error);
@@ -74,7 +74,7 @@ export function AlertRulesTab({
const handleBatchDelete = async () => {
if (selectedIds.length === 0) return;
const ok = await confirm({
title: i18n.t('monitor.alertrulestabts.sbb457b70'),
title: i18n.t('删除告警规则'),
description: `确定删除选中的 ${selectedIds.length} 条告警规则吗?此操作不可撤销。`,
confirmText: i18n.t('system.terminalBatchDelete'),
variant: 'destructive',
@@ -115,8 +115,8 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
<thead className="bg-[var(--dg-bg-tertiary)] border-b border-[var(--dg-border)]"><tr>
{[
{ f: 'hostname' as SortField, l: i18n.t('kubernetes.workload.hostname') },{ f: 'cpu_usage' as SortField, l: 'CPU' },{ f: 'memory_usage' as SortField, l: i18n.t('kubernetes.pod.memory') },
{ f: 'disk_usage' as SortField, l: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('monitor.prometheusdashb.s361ffa7f') },
{ f: 'net_out_bps' as SortField, l: i18n.t('monitor.prometheusdashb.s37cc0a9a') },{ f: 'problem_count' as SortField, l: i18n.t('monitor.zabbixdashboard.s5dc99f6e') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') },
{ f: 'disk_usage' as SortField, l: i18n.t('monitor.dashboard.col.disk') },{ f: 'load5' as SortField, l: 'Load5' },{ f: 'net_in_bps' as SortField, l: i18n.t('磁盘') },
{ f: 'net_out_bps' as SortField, l: i18n.t('流出') },{ f: 'problem_count' as SortField, l: i18n.t('问题') },{ f: 'uptime_days' as SortField, l: i18n.t('automation.scriptRun') },
].map(({ f, l }) => (
<th key={f} className="px-3 py-2 text-left font-medium text-[var(--dg-text-secondary)] cursor-pointer hover:text-[var(--dg-text-primary)] select-none whitespace-nowrap" onClick={() => toggleSort(f)}>
<div className="flex items-center gap-1">{l}<SI f={f} /></div></th>
@@ -34,7 +34,7 @@ export default function PrometheusDatasourceManager() {
const [editingDs, setEditingDs] = useState<PromDS | null>(null);
useEffect(() => {
if (isError) toast.error(i18n.t('monitor.zabbixdatasourc.s45f81749'));
if (isError) toast.error(i18n.t('获取数据源列表失败'));
}, [isError]);
const openCreate = () => {
@@ -67,7 +67,7 @@ export default function PrometheusDatasourceManager() {
await setDefaultMutation.mutateAsync(ds.id);
toast.success(`已将「${ds.name}」设为默认数据源`);
} catch {
toast.error(i18n.t('monitor.zabbixdatasourc.s9f96034c'));
toast.error(i18n.t('删除失败'));
}
};
@@ -95,8 +95,8 @@ export default function PrometheusDatasourceManager() {
{sources.length === 0 ? (
<PageEmpty
icon={<Database className="h-12 w-12" />}
title={i18n.t('monitor.prometheusdatas.s99ed81c8')}
description={i18n.t('monitor.prometheusdatas.sd9a356b8')}
title={i18n.t('暂无 Prometheus 数据源')}
description={i18n.t('添加数据源后可用于指标监控与监控大屏')}
action={(
<Button onClick={openCreate} size="sm" className="dg-btn-primary">
<Plus className="w-4 h-4 mr-1" />添加数据源
@@ -79,7 +79,7 @@ export default function ZabbixDatasourceManager() {
const res: JsonData = await zabbixDatasourceApi.list();
setSources(res?.data || res || []);
} catch {
toast.error(i18n.t('monitor.zabbixdatasourc.s45f81749'));
toast.error(i18n.t('获取数据源列表失败'));
} finally {
setLoading(false);
}
@@ -117,17 +117,17 @@ export default function ZabbixDatasourceManager() {
const handleSave = async () => {
if (!form.name.trim()) { toast.error(i18n.t('monitor.promDs.nameRequired')); return; }
if (!form.url.trim()) { toast.error(i18n.t('monitor.zabbixdatasourc.s2ba385d6')); return; }
if (!form.url.trim()) { toast.error(i18n.t('请输入 Zabbix API URL')); return; }
if (!form.username.trim()) { toast.error(i18n.t('pipeline.usernameRequired')); return; }
if (!form.password.trim()) { toast.error(i18n.t('login.passwordRequired')); return; }
try {
if (editingId) {
await zabbixDatasourceApi.update(editingId, form);
toast.success(i18n.t('monitor.zabbixdatasourc.s8dfdd2f4'));
toast.success(i18n.t('数据源已更新'));
} else {
await zabbixDatasourceApi.create(form);
toast.success(i18n.t('monitor.zabbixdatasourc.sa6def01f'));
toast.success(i18n.t('数据源已更新'));
}
setDialogOpen(false);
fetchList();
@@ -155,12 +155,12 @@ export default function ZabbixDatasourceManager() {
await zabbixDatasourceApi.setDefault(ds.id);
toast.success(`已将「${ds.name}」设为默认`);
fetchList();
} catch { toast.error(i18n.t('monitor.zabbixdatasourc.s9f96034c')); }
} catch { toast.error(i18n.t('删除失败')); }
};
const handleTest = async () => {
if (!form.url.trim() || !form.username.trim()) {
toast.error(i18n.t('monitor.zabbixdatasourc.s1d3562b7'));
toast.error(i18n.t('获取数据源列表失败'));
return;
}
setTesting(true);
@@ -264,7 +264,7 @@ export default function ZabbixDatasourceManager() {
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
<Database className="h-5 w-5 dg-text-orange" />
{editingId ? i18n.t('monitor.zabbixdatasourc.sc1c87ed6') : i18n.t('monitor.zabbixdatasourc.sa77d384f')}
{editingId ? i18n.t('编辑 Zabbix 数据源') : i18n.t('新建 Zabbix 数据源')}
</DialogTitle>
<DialogDescription>
配置 Zabbix API 连接,用于主机监控与告警数据获取
@@ -276,7 +276,7 @@ export default function ZabbixDatasourceManager() {
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-xs text-[var(--dg-text-secondary)]"> {i18n.t('deploy.form.nameStar')} </Label>
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('monitor.zabbixdatasourc.s9f1e6a92')}
<Input className="mt-1 h-9 text-sm bg-[var(--dg-bg-primary)]" placeholder={i18n.t('如:生产 Zabbix')}
value={form.name} onChange={e => updateForm('name', e.target.value)} />
</div>
<div className="flex items-end pb-1">
@@ -366,7 +366,7 @@ export default function ZabbixDatasourceManager() {
onClick={() => setShowOther(!showOther)}>
<div className="flex items-center gap-2">
<Settings className="w-4 h-4" />
<span> {i18n.t('monitor.zabbixdatasourc.sdda36edb')} </span>
<span> {i18n.t('编辑 Zabbix 数据源')} </span>
</div>
{showOther ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</button>
@@ -147,10 +147,10 @@ export function LogDatasourcePage() {
rowKey={(ds) => ds.id}
loading={loading}
searchable
searchPlaceholder={i18n.t('monitor.logdatasourcepa.seb5e4d30')}
searchPlaceholder={i18n.t('搜索数据源名称或地址...')}
searchKeys={['name', 'url']}
sortable
emptyText={i18n.t('monitor.logdatasourcepa.sc8a96073')}
emptyText={i18n.t('暂无数据源,点击上方按钮添加')}
toolbarExtra={
<>
<Button variant="outline" size="sm" className="h-8 border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:bg-[var(--dg-bg-hover)] hover:text-[var(--dg-text-primary)]" onClick={load}>
@@ -65,7 +65,7 @@ export function LogFieldSidebar({
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder={i18n.t('monitor.logfieldsidebar.s2d3013c3')}
placeholder={i18n.t('})${f.count ? ` · ${f.count} 个值` : ')}
className="w-full h-7 pl-7 pr-2 text-xs rounded-md dg-input"
style={{ background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)' }}
/>
@@ -24,8 +24,8 @@ interface Props {
}
const STATUS_LABEL: Record<string, { text: string; color: string }> = {
precise: { text: i18n.t('monitor.logquerytoolbar.sd0368470'), color: '#22c55e' },
partial: { text: i18n.t('monitor.logquerytoolbar.s8b09f4fe'), color: '#f59e0b' },
precise: { text: i18n.t('结果精确'), color: '#22c55e' },
partial: { text: i18n.t('部分命中'), color: '#f59e0b' },
failed: { text: i18n.t('monitor.logPanel.queryFailed'), color: '#ef4444' },
};
@@ -22,7 +22,7 @@ import i18n from '@/i18n';
const ROLE_LABEL: Record<string, string> = {
viewer: i18n.t('pipeline.viewer'),
analyst: i18n.t('monitor.logrbactsx.sa72dec16'),
analyst: i18n.t('分析者'),
};
const ENGINE_LABEL: Record<string, string> = {
@@ -108,7 +108,7 @@ export default function LogRBAC() {
const handleCreate = async () => {
if (!newName.trim() || !newCode.trim() || !newPattern.trim()) {
toast.error(i18n.t('monitor.logrbactsx.sf6e80bf0')); return;
toast.error(i18n.t('请填写名称、标识与正则')); return;
}
setSaving(true);
try {
@@ -147,7 +147,7 @@ export default function LogRBAC() {
const handleDelete = async (role: LogScopeRole) => {
const ok = await _confirm({
title: i18n.t('monitor.logrbactsx.s5d79e436'),
title: i18n.t('删除权限'),
description: `确定删除「${role.name}」?将影响 ${matchCount[role.id]?.length || 0} 个示例范围`,
variant: 'destructive',
});
@@ -165,7 +165,7 @@ export default function LogRBAC() {
};
const handleAssign = async () => {
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('monitor.logrbactsx.s0fc53985')); return; }
if (!selectedUserId || !assignRoleId) { toast.error(i18n.t('请选择用户和权限')); return; }
setAssignLoading(true);
try {
await logRbacApi.assignRole(Number(selectedUserId), {
@@ -189,8 +189,8 @@ export default function LogRBAC() {
const selectedUser = users.find((u) => String(u.id) === selectedUserId);
const pageTabs = [
{ id: 'roles', label: i18n.t('monitor.logrbactsx.s93ef07cd'), badge: roles.length || undefined },
{ id: 'users', label: i18n.t('monitor.logrbactsx.s2de89571') },
{ id: 'roles', label: i18n.t('权限策略'), badge: roles.length || undefined },
{ id: 'users', label: i18n.t('用户授权') },
{ id: 'preview', label: i18n.t('pipeline.matchPreview') },
];
@@ -215,8 +215,8 @@ export default function LogRBAC() {
roles.length === 0 ? (
<PageEmpty
icon={<Shield className="h-10 w-10" />}
title={i18n.t('monitor.logrbactsx.s4a00d253')}
description={i18n.t('monitor.logrbactsx.sd11b7407')}
title={i18n.t('暂无日志权限策略')}
description={i18n.t('创建策略并分配给用户后,将按正则限制可查询的索引与 Label 范围')}
action={<Button onClick={() => setCreateOpen(true)}><Plus className="h-4 w-4 mr-1" /> {i18n.t('monitor.logrbactsx.s6058845c')} </Button>}
/>
) : (
@@ -259,7 +259,7 @@ export default function LogRBAC() {
<SelectTrigger className="h-7 w-20 text-xs"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="viewer"> {i18n.t('pipeline.viewer')} </SelectItem>
<SelectItem value="analyst"> {i18n.t('monitor.logrbactsx.sa72dec16')} </SelectItem>
<SelectItem value="analyst"> {i18n.t('分析者')} </SelectItem>
</SelectContent>
</Select>
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
@@ -302,7 +302,7 @@ export default function LogRBAC() {
</Select>
</div>
<div className="space-y-1 w-48">
<Label className="text-xs"> {i18n.t('monitor.logrbactsx.s93ef07cd')} </Label>
<Label className="text-xs"> {i18n.t('权限策略')} </Label>
<Select value={assignRoleId} onValueChange={setAssignRoleId}>
<SelectTrigger className="h-8 text-sm dg-input"><SelectValue placeholder={i18n.t('kubernetes.hpa.selectPolicy')} /></SelectTrigger>
<SelectContent>
@@ -4,36 +4,36 @@ import i18n from '@/i18n';
const ALL_TEMPLATES: Record<'loki' | 'elasticsearch', { label: string; query: string }[]> = {
loki: [
{ label: i18n.t('monitor.quickquerybarts.s64559524'), query: '{job="nginx"} |= "error"' },
{ label: i18n.t('monitor.quickquerybarts.s315990ac'), query: '{namespace="prod"} | json | level="warn"' },
{ label: i18n.t('monitor.quickquerybarts.sfc6d4276'), query: '{app="api"} |= "timeout"' },
{ label: i18n.t('monitor.quickquerybarts.s0ce3d219'), query: 'sum by(level) (count_over_time({job="app"}[5m]))' },
{ label: i18n.t('monitor.quickquerybarts.s6209b154'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
{ label: i18n.t('monitor.quickquerybarts.sf839efd6'), query: '{job="api"} | json | response_time > 1000' },
{ label: i18n.t('monitor.quickquerybarts.s386c7580'), query: 'sum by(level) (count_over_time({app="cex-admin-api"} |= "error" [5m]))' },
{ label: i18n.t('monitor.quickquerybarts.sf94f3fdb'), query: '{app="cex-admin-api"} |= "trace_id" | json' },
{ label: i18n.t('monitor.quickquerybarts.s7f0fa5fa'), query: 'sum by(service_name) (count_over_time({namespace="astralx"}[5m]))' },
{ label: i18n.t('monitor.quickquerybarts.s49c7620d'), query: '{app="cex-admin-api"} | json | response_time > 500' },
{ label: i18n.t('monitor.quickquerybarts.sb8edf48d'), query: '{app="cex-admin-api"} |= "Exception"' },
{ label: i18n.t('Nginx 错误'), query: '{job="nginx"} |= "error"' },
{ label: i18n.t('JSON 过滤'), query: '{namespace="prod"} | json | level="warn"' },
{ label: i18n.t('超时搜索'), query: '{app="api"} |= "timeout"' },
{ label: i18n.t('级别统计'), query: 'sum by(level) (count_over_time({job="app"}[5m]))' },
{ label: i18n.t('正则多级别'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
{ label: i18n.t('慢请求>1s'), query: '{job="api"} | json | response_time > 1000' },
{ label: i18n.t('错误率统计'), query: 'sum by(level) (count_over_time({app="cex-admin-api"} |= "error" [5m]))' },
{ label: i18n.t('追踪ID查询'), query: '{app="cex-admin-api"} |= "trace_id" | json' },
{ label: i18n.t('错误率统计'), query: 'sum by(service_name) (count_over_time({namespace="astralx"}[5m]))' },
{ label: i18n.t('高延迟筛选'), query: '{app="cex-admin-api"} | json | response_time > 500' },
{ label: i18n.t('异常堆栈'), query: '{app="cex-admin-api"} |= "Exception"' },
{ label: i18n.t('monitor.logTrend'), query: 'sum(count_over_time({app="cex-admin-api"}[1h]))' },
{ label: i18n.t('monitor.quickquerybarts.s1ace0c03'), query: '{app="cex-admin-api"} | regexp "(?P<method>[A-Za-z0-9_]+) (?P<path>[^ ]+)"' },
{ label: i18n.t('monitor.quickquerybarts.s20b34227'), query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' },
{ label: i18n.t('正则提取'), query: '{app="cex-admin-api"} | regexp "(?P<method>[A-Za-z0-9_]+) (?P<path>[^ ]+)"' },
{ label: i18n.t('格式输出'), query: '{app="cex-admin-api"} | json | line_format "{{.level}} {{.message}}"' },
],
elasticsearch: [
{ label: 'API 5xx', query: 'status:500 AND path:"/api/*"' },
{ label: i18n.t('monitor.quickquerybarts.sa1eb8196'), query: '(level:error OR log.level:error) AND message:*timeout*' },
{ label: i18n.t('monitor.quickquerybarts.s0235c6eb'), query: 'host.name:server-* AND NOT level:debug' },
{ label: i18n.t('monitor.quickquerybarts.s0876c43d'), query: 'response_time:>1000 AND method:POST' },
{ label: i18n.t('monitor.quickquerybarts.se31850bf'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
{ label: i18n.t('monitor.quickquerybarts.s6241b884'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
{ label: i18n.t('monitor.quickquerybarts.s04adb0fd'), query: 'app:cex-admin-api AND (level:(error OR warn) OR log.level:(error OR warn))' },
{ label: i18n.t('monitor.quickquerybarts.sc579cfbb'), query: 'message:*timeout* AND app:cex-admin-api' },
{ label: i18n.t('monitor.quickquerybarts.s0c9d7886'), query: 'response_time:[1000 TO *] AND method:POST' },
{ label: i18n.t('monitor.quickquerybarts.se11dceb4'), query: 'app:cex-admin-api AND NOT path:/health' },
{ label: i18n.t('monitor.quickquerybarts.s00f6f739'), query: '@timestamp:[now-1h TO now] AND level:error' },
{ label: i18n.t('monitor.quickquerybarts.s8a1879ae'), query: 'client_ip:172.36.* AND app:cex-admin-api' },
{ label: i18n.t('monitor.quickquerybarts.s6f401985'), query: 'app:cex-admin-api AND (level:(error OR warn OR info) OR log.level:(error OR warn OR info))' },
{ label: i18n.t('monitor.quickquerybarts.sf09e007f'), query: 'labels.orgId:10001 AND app:cex-admin-api' },
{ label: i18n.t('错误超时'), query: '(level:error OR log.level:error) AND message:*timeout*' },
{ label: i18n.t('排除调试'), query: 'host.name:server-* AND NOT level:debug' },
{ label: i18n.t('慢请求'), query: 'response_time:>1000 AND method:POST' },
{ label: i18n.t('严重错误'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
{ label: i18n.t('严重错误'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
{ label: i18n.t('组合查询'), query: 'app:cex-admin-api AND (level:(error OR warn) OR log.level:(error OR warn))' },
{ label: i18n.t('模糊匹配'), query: 'message:*timeout* AND app:cex-admin-api' },
{ label: i18n.t('范围查询'), query: 'response_time:[1000 TO *] AND method:POST' },
{ label: i18n.t('排除路径'), query: 'app:cex-admin-api AND NOT path:/health' },
{ label: i18n.t('时间过滤'), query: '@timestamp:[now-1h TO now] AND level:error' },
{ label: i18n.t('IP段过滤'), query: 'client_ip:172.36.* AND app:cex-admin-api' },
{ label: i18n.t('多级别筛选'), query: 'app:cex-admin-api AND (level:(error OR warn OR info) OR log.level:(error OR warn OR info))' },
{ label: i18n.t('嵌套字段'), query: 'labels.orgId:10001 AND app:cex-admin-api' },
],
};
@@ -6,27 +6,27 @@ import i18n from '@/i18n';
// 相对时间预设:最近N... 每次点击时计算 start = now - N // Logan.Li
const RELATIVE_PRESETS = [
{ label: i18n.t('monitor.timerangepicker.s6b3f2ecb'), value: '1m' },
{ label: i18n.t('最近1分钟'), value: '1m' },
{ label: i18n.t('monitor.logPanel.last5min'), value: '5m' },
{ label: i18n.t('monitor.timerangepicker.sd4962f4b'), value: '15m' },
{ label: i18n.t('monitor.timerangepicker.sd8a81545'), value: '1h' },
{ label: i18n.t('monitor.timerangepicker.s0823ad83'), value: '4h' },
{ label: i18n.t('monitor.timerangepicker.s77be0dba'), value: '24h' },
{ label: i18n.t('monitor.timerangepicker.s2ba41ef4'), value: '7d' },
{ label: i18n.t('monitor.timerangepicker.s63f66f1f'), value: '30d' },
{ label: i18n.t('最近15分钟'), value: '15m' },
{ label: i18n.t('最近1小时'), value: '1h' },
{ label: i18n.t('最近4小时'), value: '4h' },
{ label: i18n.t('最近1天'), value: '24h' },
{ label: i18n.t('最近1周'), value: '7d' },
{ label: i18n.t('最近30天'), value: '30d' },
];
// 绝对时间预设:固定的自然日/周/月/季/年 // Logan.Li
const ABSOLUTE_PRESETS = [
{ label: i18n.t('audit.terminal.presetToday'), value: 'today' },
{ label: i18n.t('common.yesterday'), value: 'yesterday' },
{ label: i18n.t('monitor.timerangepicker.s94995c31'), value: 'day_before_yesterday' },
{ label: i18n.t('前天'), value: 'day_before_yesterday' },
{ label: i18n.t('common.thisWeek'), value: 'this_week' },
{ label: i18n.t('common.lastWeek'), value: 'last_week' },
{ label: i18n.t('cloud.thisMonth'), value: 'this_month' },
{ label: i18n.t('common.lastMonth'), value: 'last_month' },
{ label: i18n.t('monitor.timerangepicker.s0477ce74'), value: 'this_quarter' },
{ label: i18n.t('monitor.timerangepicker.s77df6c8e'), value: 'this_year' },
{ label: i18n.t('本季度'), value: 'this_quarter' },
{ label: i18n.t('本年度'), value: 'this_year' },
];
function getPresetRange(value: string): { start: Date; end: Date; label: string } {
@@ -136,7 +136,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
{/* 头部 */}
<div className="flex items-center gap-2 mb-3 pb-3" style={{ borderBottom: '1px solid var(--dg-border)' }}>
<Calendar className="h-4 w-4" style={{ color: 'var(--dg-blue)' }} />
<span className="text-sm font-medium" style={{ color: 'var(--dg-text-primary)' }}> {i18n.t('monitor.timerangepicker.sc2c8fb23')} </span>
<span className="text-sm font-medium" style={{ color: 'var(--dg-text-primary)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </span>
<button
onClick={openCustom}
className="text-xs ml-2 px-2 py-0.5 rounded transition-colors"
@@ -152,7 +152,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
{mode === 'preset' ? (
<>
{/* 相对时间 — 最近... */}
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: '#22c55e' }}> {i18n.t('monitor.timerangepicker.s8ead0af2')} </p>
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: '#22c55e' }}> {i18n.t(' /> 另存为 <ChevronDown className=')} </p>
<div className="grid grid-cols-4 gap-1.5 mb-3">
{RELATIVE_PRESETS.map(p => (
<button
@@ -171,7 +171,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
</div>
{/* 绝对时间 — 固定日历范围 */}
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: 'var(--dg-blue)' }}> {i18n.t('monitor.timerangepicker.s714f6cdd')} </p>
<p className="text-[10px] mb-2 font-medium uppercase tracking-wide" style={{ color: 'var(--dg-blue)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </p>
<div className="grid grid-cols-5 gap-1.5">
{ABSOLUTE_PRESETS.map(p => (
<button
@@ -192,7 +192,7 @@ export function TimeRangePicker({ value, onChange }: Props) {
) : (
/* 自定义日历模式 */
<div className="space-y-3">
<p className="text-[10px] font-medium uppercase" style={{ color: 'var(--dg-blue)' }}> {i18n.t('monitor.hosttimerangepi.s0122e022')} </p>
<p className="text-[10px] font-medium uppercase" style={{ color: 'var(--dg-blue)' }}> {i18n.t('开启后折叠行仅显示前 500 字预览')} </p>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-[10px] block mb-1" style={{ color: 'var(--dg-text-muted)' }}> {i18n.t('system.terminalStartTime')} </label>
@@ -134,7 +134,7 @@ export const ENGINE_META: Record<LogEngine, { name: string; color: string; query
color: '#f97316',
queryPlaceholder: 'keyword | SELECT * WHERE level = "info"',
statPlaceholder: '* | SELECT level, COUNT(*) as cnt GROUP BY level',
externalLink: i18n.t('monitor.typesv3ts.sfe4ff0b4'),
externalLink: i18n.t('SLS控制台'),
},
};
@@ -250,27 +250,27 @@ export const savedQueryStorage = {
// 快捷查询模板 // Logan.Li
export const QUICK_TEMPLATES: Record<LogEngine, { label: string; query: string }[]> = {
loki: [
{ label: i18n.t('monitor.typesv3ts.s70d6c24a'), query: '{job="nginx"} |= "error"' },
{ label: i18n.t('monitor.typesv3ts.saf1cb054'), query: '{namespace="prod"} | json | level="warn"' },
{ label: i18n.t('monitor.quickquerybarts.sfc6d4276'), query: '{app="api"} |= "timeout"' },
{ label: i18n.t('monitor.typesv3ts.s475a42fe'), query: 'sum by (level) (count_over_time({job="app"}[5m]))' },
{ label: i18n.t('monitor.typesv3ts.sac8a5c83'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
{ label: i18n.t('monitor.typesv3ts.s4dc793a9'), query: '{job="api"} | json | response_time > 1000' },
{ label: i18n.t('Nginx 错误日志'), query: '{job="nginx"} |= "error"' },
{ label: i18n.t('JSON 解析过滤'), query: '{namespace="prod"} | json | level="warn"' },
{ label: i18n.t('超时搜索'), query: '{app="api"} |= "timeout"' },
{ label: i18n.t('按级别统计'), query: 'sum by (level) (count_over_time({job="app"}[5m]))' },
{ label: i18n.t('正则匹配多级别'), query: '{container="frontend"} |~ "ERROR|WARN|FATAL"' },
{ label: i18n.t('慢请求 >1s'), query: '{job="api"} | json | response_time > 1000' },
],
elasticsearch: [
{ label: i18n.t('monitor.typesv3ts.sd81428f4'), query: 'status:500 AND path:"/api/*"' },
{ label: i18n.t('monitor.typesv3ts.s6624c787'), query: '(level:error OR log.level:error) AND message:*timeout*' },
{ label: i18n.t('monitor.typesv3ts.s98b327ac'), query: 'host.name:server-* AND NOT level:debug AND NOT log.level:debug' },
{ label: i18n.t('monitor.typesv3ts.s95d72fa1'), query: 'response_time:>1000 AND method:POST' },
{ label: i18n.t('monitor.typesv3ts.s9a0d4c8c'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
{ label: i18n.t('monitor.typesv3ts.s68f58cc0'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
{ label: i18n.t('API 5xx 错误'), query: 'status:500 AND path:"/api/*"' },
{ label: i18n.t('错误超时日志'), query: '(level:error OR log.level:error) AND message:*timeout*' },
{ label: i18n.t('排除调试日志'), query: 'host.name:server-* AND NOT level:debug AND NOT log.level:debug' },
{ label: i18n.t('慢请求检测'), query: 'response_time:>1000 AND method:POST' },
{ label: i18n.t('最近1小时严重错误'), query: '@timestamp:[now-1h TO now] AND (level:critical OR log.level:critical)' },
{ label: i18n.t('最近1小时严重错误'), query: 'service:nginx AND (level:(error OR fatal) OR log.level:(error OR fatal))' },
],
sls: [
{ label: i18n.t('monitor.typesv3ts.sf8889aa4'), query: "* | select * where level = 'error'" },
{ label: i18n.t('monitor.typesv3ts.s475a42fe'), query: "* | select count(*) as cnt, level group by level" },
{ label: i18n.t('monitor.typesv3ts.s27ee0975'), query: "* | select date_trunc('minute', __time__) as t, count(*) group by t order by t" },
{ label: i18n.t('monitor.typesv3ts.sa5ad7487'), query: "* | select method, count(*) as cnt, avg(response_time) group by method order by cnt desc" },
{ label: i18n.t('monitor.typesv3ts.s80428bd5'), query: "* | select * where response_time > 1000 order by response_time desc limit 100" },
{ label: i18n.t('monitor.typesv3ts.s63c5c72e'), query: "* | select status_code, count(*) as cnt, count(*) * 100.0 / sum(count(*)) over() as pct group by status_code" },
{ label: i18n.t('错误日志筛选'), query: "* | select * where level = 'error'" },
{ label: i18n.t('按级别统计'), query: "* | select count(*) as cnt, level group by level" },
{ label: i18n.t('时间聚合'), query: "* | select date_trunc('minute', __time__) as t, count(*) group by t order by t" },
{ label: i18n.t('状态码分布'), query: "* | select method, count(*) as cnt, avg(response_time) group by method order by cnt desc" },
{ label: i18n.t('慢请求分析'), query: "* | select * where response_time > 1000 order by response_time desc limit 100" },
{ label: i18n.t('状态码分布'), query: "* | select status_code, count(*) as cnt, count(*) * 100.0 / sum(count(*)) over() as pct group by status_code" },
],
};