批量转换 163 个文件,3204 处中文替换为 t() 调用: - Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换 - Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换 - Deploy: 流水线/发布/凭证/RBAC 全部转换 - Server/Cloud/Agent/Task/Layout/UI: 全部转换 - 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys) - tsc 编译零错误
191 lines
8.0 KiB
TypeScript
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>
|
|
);
|
|
} |