使用 i18n.t() 全局方式(避免 hook 定位问题): - 288 目标文件中 135 个成功转换 - 2228 处中文替换为 i18n.t() 调用 - 1006 个新翻译 key 添加到 zh-CN.json + en-US.json - 词典总量: 3554 keys (22 namespaces) - tsc 零错误 + Vite 构建成功 - 6 个含代码示例/数据字符串的文件安全回退
120 lines
5.2 KiB
TypeScript
120 lines
5.2 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
// LogAlertsPanel — 日志告警规则列表 // Logan.Li
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Plus, Pencil, Trash2, RefreshCw, Loader2 } from 'lucide-react';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
import { useConfirm } from '@/components/ui/confirm-dialog';
|
|
import { alertApi } from '@/api';
|
|
import { AlertRuleDialog } from '@/components/monitor/AlertRuleDialog';
|
|
import i18n from '@/i18n';
|
|
|
|
export function LogAlertsPanel() {
|
|
const { toast } = useToast();
|
|
const confirm = useConfirm();
|
|
const [rules, setRules] = useState<any[]>([]);
|
|
const [channels, setChannels] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
const [editing, setEditing] = useState<any>(null);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [rulesRes, chRes] = await Promise.all([
|
|
alertApi.getRules(),
|
|
alertApi.getNotifyChannels(),
|
|
]);
|
|
const all = (rulesRes as any)?.list || (Array.isArray(rulesRes) ? rulesRes : []);
|
|
setRules(all.filter((r: any) => r.type === 'log' || r.source === 'loki' || r.source === 'elasticsearch'));
|
|
setChannels((chRes as any)?.list || []);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
const handleDelete = async (rule: any) => {
|
|
const ok = await confirm({ title: i18n.t('monitor.deleteRule'), description: `删除「${rule.name}」?`, variant: 'destructive', confirmText: i18n.t('kubernetes.storage.delete2') });
|
|
if (!ok) return;
|
|
try {
|
|
await alertApi.deleteRule(rule.id);
|
|
toast({ title: i18n.t('monitor.silenceRules.deleted') });
|
|
load();
|
|
} catch (e: any) {
|
|
toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: e?.message || String(e), variant: 'destructive' });
|
|
}
|
|
};
|
|
|
|
const severityColor: Record<string, string> = {
|
|
critical: 'bg-red-500/15 dg-text-red',
|
|
warning: 'bg-amber-500/15 dg-text-yellow',
|
|
info: 'bg-blue-500/15 dg-text-blue',
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-sm text-[var(--dg-text-muted)]"> {i18n.t('monitor.logalertspanelt.s5bc514d8')} </p>
|
|
<div className="flex gap-2">
|
|
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
|
|
<RefreshCw className={`h-4 w-4 mr-1 ${loading ? 'animate-spin' : ''}`} />刷新
|
|
</Button>
|
|
<Button size="sm" onClick={() => { setEditing(null); setDialogOpen(true); }}>
|
|
<Plus className="h-4 w-4 mr-1" />新建
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex justify-center py-16"><Loader2 className="h-6 w-6 animate-spin text-[var(--dg-text-muted)]" /></div>
|
|
) : rules.length === 0 ? (
|
|
<Card className="dg-card">
|
|
<CardContent className="py-12 text-center text-[var(--dg-text-muted)] text-sm">
|
|
暂无日志告警。在 Loki/ES 查询页点击右下角「叮当」可从当前查询快速创建。
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{rules.map(rule => (
|
|
<Card key={rule.id} className="dg-card">
|
|
<CardContent className="py-3 px-4 flex flex-wrap items-center gap-3">
|
|
<div className="flex-1 min-w-[200px]">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<span className="font-medium text-[var(--dg-text-primary)]">{rule.name}</span>
|
|
<Badge variant="outline" className="text-[10px] border-[var(--dg-border)]">{rule.source || 'log'}</Badge>
|
|
<Badge className={`text-[10px] ${severityColor[rule.severity] || ''}`}>{rule.severity}</Badge>
|
|
{!rule.enabled && rule.enabled !== undefined && (
|
|
<Badge variant="outline" className="text-[10px] text-[var(--dg-text-muted)]"> {i18n.t('aiops.llmDisabled')} </Badge>
|
|
)}
|
|
</div>
|
|
<p className="text-[11px] font-mono text-[var(--dg-text-muted)] mt-1 truncate" title={rule.expression}>{rule.expression}</p>
|
|
</div>
|
|
<div className="flex gap-1 shrink-0">
|
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => { setEditing(rule); setDialogOpen(true); }}>
|
|
<Pencil className="h-3.5 w-3.5" />
|
|
</Button>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-red" onClick={() => handleDelete(rule)}>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<AlertRuleDialog
|
|
open={dialogOpen}
|
|
onOpenChange={setDialogOpen}
|
|
editingRule={editing}
|
|
notifyChannels={channels}
|
|
onSaved={load}
|
|
defaultType="log"
|
|
/>
|
|
</div>
|
|
);
|
|
} |