feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心 - 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog) - 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则 - 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp) 监控采集 - SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect) - 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed) - 站点探测 pkg(siteprobe) + 站点视图(site_view) 自动化任务 - cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task) 审计与凭证 - 审计设置(audit_setting) + 历史清理(auditcleaner) 前端 - 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化 - 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染 - 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
092d0e4b9e
commit
471353e3d7
190 files changed
+16640
-3484
No files matched your search
@@ -0,0 +1,188 @@
|
||||
// 告警规则 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';
|
||||
|
||||
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: '主机监控', icon: Server, desc: 'Agent CPU/内存/磁盘/离线' },
|
||||
{ id: 'middleware', label: '中间件', icon: Database, desc: 'Redis / MySQL 指标' },
|
||||
{ id: 'snmp', label: 'SNMP', icon: Network, desc: '网络设备 / BMC' },
|
||||
{ id: 'site', label: '站点拨测', icon: Globe, desc: 'HTTP/HTTPS 可用性与证书' },
|
||||
];
|
||||
|
||||
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 [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="加载规则..." />;
|
||||
|
||||
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={`暂无${catMeta.label}规则`} description="点击「添加规则」或「默认规则」创建" />
|
||||
) : (
|
||||
<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 ? '启用' : '停用'}
|
||||
</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);
|
||||
}
|
||||
}}>编辑</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 text-xs dg-text-red"
|
||||
onClick={() => rule.rule_type === 'agent' ? onDeleteAgent(rule) : onDeleteMonitor(rule)}>删除</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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user