Files
aiops/frontend/src/components/monitor/alert-center/AlertRulesTab.tsx
T
attacker ecec73989b feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())
批量转换 163 个文件,3204 处中文替换为 t() 调用:
- Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换
- Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换
- Deploy: 流水线/发布/凭证/RBAC 全部转换
- Server/Cloud/Agent/Task/Layout/UI: 全部转换
- 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys)
- tsc 编译零错误
2026-07-03 01:51:57 +08:00

191 lines
8.0 KiB
TypeScript

// 告警规则 Tab — 主机/中间件/SNMP/站点 四类分栏 // Logan.Li
import { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Plus, Rocket, Server, Database, Network, Globe } from 'lucide-react';
import { PageEmpty } from '@/components/layout/PageEmpty';
import { PageLoading } from '@/components/layout/PageLoading';
import { getSeverityBadge } from './alert-center-helpers';
import { AgentRuleDialog } from '@/components/agent/AgentRuleDialog';
import { MonitorRuleDialog } from './MonitorRuleDialog';
import type { MetricCatalogCategory, UnifiedAlertRule } from '@/api';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
type RuleCategory = 'host' | 'middleware' | 'snmp' | 'site';
type MonitorCategory = 'middleware' | 'snmp' | 'site';
const RULE_CATEGORIES: Array<{
id: RuleCategory;
label: string;
icon: typeof Server;
desc: string;
}> = [
{ id: 'host', label: i18n.t('monitor.hostMonitor'), icon: Server, desc: i18n.t('monitor.alertrulestab.s836f7b') },
{ id: 'middleware', label: i18n.t('dashboard.ops.middleware'), icon: Database, desc: i18n.t('monitor.alertrulestab.sf3e5db') },
{ id: 'snmp', label: 'SNMP', icon: Network, desc: i18n.t('monitor.alertrulestab.s086e4c') },
{ id: 'site', label: i18n.t('monitor.siteProbe'), icon: Globe, desc: i18n.t('monitor.alertrulestab.sf9f400') },
];
interface Props {
rules: UnifiedAlertRule[];
loading: boolean;
catalog: MetricCatalogCategory[];
onRefresh: () => void;
onToggleAgent: (id: number, enabled: boolean) => void;
onDeleteAgent: (rule: UnifiedAlertRule) => void;
onDeleteMonitor: (rule: UnifiedAlertRule) => void;
onToggleMonitor: (id: number, enabled: boolean) => void;
onQuickSetupHost: () => void;
onQuickSetupMonitor: (cat: string) => void;
quickSetupPending: boolean;
}
export function AlertRulesTab({
rules, loading, catalog, onRefresh,
onToggleAgent, onDeleteAgent, onDeleteMonitor, onToggleMonitor,
onQuickSetupHost, onQuickSetupMonitor, quickSetupPending,
}: Props) {
const { t } = useTranslation();
const [activeCat, setActiveCat] = useState<RuleCategory>('host');
const [agentDialogOpen, setAgentDialogOpen] = useState(false);
const [monitorDialogOpen, setMonitorDialogOpen] = useState(false);
const [monitorCategory, setMonitorCategory] = useState<MonitorCategory>('middleware');
const [editingAgent, setEditingAgent] = useState<JsonData>(null);
const [editingMonitor, setEditingMonitor] = useState<UnifiedAlertRule | null>(null);
const filtered = rules.filter((r) => r.category === activeCat);
const catMeta = RULE_CATEGORIES.find((c) => c.id === activeCat)!;
const getMetricsFor = (cat: MonitorCategory) =>
catalog.find((c) => c.id === cat)?.metrics || [];
const openAdd = () => {
if (activeCat === 'host') {
setEditingAgent(null);
setAgentDialogOpen(true);
return;
}
setMonitorCategory(activeCat as MonitorCategory);
setEditingMonitor(null);
setMonitorDialogOpen(true);
};
const handleQuickSetup = () => {
if (activeCat === 'host') {
onQuickSetupHost();
} else {
onQuickSetupMonitor(activeCat);
}
};
if (loading) return <PageLoading label=t('monitor.alertrulestab.s1e637e') />;
return (
<div className="space-y-4">
<div className="flex flex-wrap gap-2">
{RULE_CATEGORIES.map((cat) => {
const Icon = cat.icon;
const count = rules.filter((r) => r.category === cat.id).length;
return (
<Button
key={cat.id}
variant={activeCat === cat.id ? 'default' : 'outline'}
size="sm"
className="h-8 text-xs gap-1.5"
onClick={() => setActiveCat(cat.id)}
>
<Icon className="h-3.5 w-3.5" />
{cat.label}
{count > 0 && (
<Badge variant="secondary" className="text-[10px] h-4 px-1">{count}</Badge>
)}
</Button>
);
})}
</div>
<div className="flex flex-wrap items-center gap-2">
<p className="text-xs text-muted-foreground flex-1 min-w-[200px]">{catMeta.desc}</p>
<Button size="sm" className="h-8" onClick={openAdd}>
<Plus className="h-3.5 w-3.5 mr-1" /> 添加规则
</Button>
<Button size="sm" variant="secondary" className="h-8" onClick={handleQuickSetup} disabled={quickSetupPending}>
<Rocket className="h-3.5 w-3.5 mr-1" /> 默认规则
</Button>
</div>
{filtered.length === 0 ? (
<PageEmpty title={t('monitor.alertrulestab.sb2f604', { catMeta.label: catMeta.label })} description=t('monitor.alertrulestab.s1f2385') />
) : (
<div className="grid grid-cols-1 gap-3">
{filtered.map((rule) => (
<Card key={`${rule.rule_type}-${rule.id}`}>
<CardContent className="p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1.5 flex-wrap">
<span className="font-medium text-sm">{rule.name}</span>
{getSeverityBadge(rule.severity)}
<Badge variant={rule.enabled ? 'default' : 'secondary'} className="text-[10px]">
{rule.enabled ? t('common.enable') : t('common.deactivate')}
</Badge>
</div>
<p className="text-xs text-muted-foreground font-mono">
{rule.metric} {rule.operator} {rule.threshold}
{rule.target ? ` · ${rule.target}` : ''}
</p>
{rule.description && (
<p className="text-[10px] text-muted-foreground mt-0.5">{rule.description}</p>
)}
</div>
<div className="flex items-center gap-1.5 shrink-0">
<Button variant="ghost" size="sm" className="h-7 text-xs"
onClick={() => {
if (rule.rule_type === 'agent') {
setEditingAgent(rule as unknown as JsonData);
setAgentDialogOpen(true);
} else {
setEditingMonitor(rule);
setMonitorCategory(rule.category as MonitorCategory);
setMonitorDialogOpen(true);
}
}}>{t('common.edit')}</Button>
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red"
onClick={() => rule.rule_type === 'agent' ? onDeleteAgent(rule) : onDeleteMonitor(rule)}>{t('common.delete')}</Button>
<Switch checked={rule.enabled}
onCheckedChange={(v) => rule.rule_type === 'agent'
? onToggleAgent(rule.id, v)
: onToggleMonitor(rule.id, v)} />
</div>
</div>
</CardContent>
</Card>
))}
</div>
)}
<AgentRuleDialog
open={agentDialogOpen}
onOpenChange={setAgentDialogOpen}
editingRule={editingAgent}
onSuccess={() => { setAgentDialogOpen(false); onRefresh(); }}
/>
<MonitorRuleDialog
open={monitorDialogOpen}
onOpenChange={setMonitorDialogOpen}
category={monitorCategory}
metrics={getMetricsFor(monitorCategory)}
editing={editingMonitor ? {
id: editingMonitor.id, name: editingMonitor.name, metric: editingMonitor.metric,
operator: editingMonitor.operator, threshold: editingMonitor.threshold,
severity: editingMonitor.severity, enabled: editingMonitor.enabled,
} : null}
onSaved={onRefresh}
/>
</div>
);
}