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:
LoganandClaude committed 2026-07-01 23:44:45 +08:00
1 parent 092d0e4b9e
commit 471353e3d7
190 files changed
+16640 -3484

No files matched your search

@@ -0,0 +1,135 @@
// SNMP 概览图表 — 设备状态分布 + CPU 对比 // Logan.Li
import { useMemo } from 'react';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, Cell, PieChart, Pie,
} from 'recharts';
import { Card, CardContent } from '@/components/ui/card';
import { BarChart3, PieChart as PieChartIcon } from 'lucide-react';
import type { SnmpDevice, SnmpOverviewItem } from './helpers';
import { fmtMetricValue, statusColor } from './helpers';
import { MonitorChartTooltip, MONITOR_PIE_STROKE, MonitorPieTooltip } from '../shared/monitorChartTheme';
interface Props {
devices: SnmpDevice[];
overview: SnmpOverviewItem[];
}
export function SnmpOverviewCharts({ devices, overview }: Props) {
const statusData = useMemo(() => {
const online = devices.filter((d) => d.status === 'online').length;
const error = devices.filter((d) => d.status === 'error').length;
const offline = devices.length - online - error;
return [
{ name: '在线', value: online, fill: 'var(--dg-green)' },
{ name: '异常', value: error, fill: 'var(--dg-red)' },
{ name: '离线', value: offline, fill: 'var(--dg-text-muted)' },
].filter((d) => d.value > 0);
}, [devices]);
const cpuData = useMemo(() => {
return overview
.filter((o) => o.cpu != null && o.cpu > 0)
.map((o) => ({
name: o.name.length > 10 ? `${o.name.slice(0, 10)}…` : o.name,
fullName: o.name,
cpu: o.cpu ?? 0,
fill: statusColor(o.status),
}))
.sort((a, b) => b.cpu - a.cpu);
}, [overview]);
if (devices.length === 0) return null;
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
<Card className="lg:col-span-2 bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<BarChart3 className="h-4 w-4 text-[var(--dg-text-muted)]" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">设备 CPU 对比</h3>
</div>
{cpuData.length === 0 ? (
<p className="text-xs text-[var(--dg-text-muted)] py-8 text-center">暂无 CPU 指标,请先完成采集</p>
) : (
<ResponsiveContainer width="100%" height={Math.max(160, cpuData.length * 36)}>
<BarChart data={cpuData} layout="vertical" margin={{ top: 0, right: 16, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--dg-border)" horizontal={false} />
<XAxis
type="number"
domain={[0, 100]}
tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }}
tickLine={false}
axisLine={{ stroke: 'var(--dg-border)' }}
tickFormatter={(v: number) => `${v}%`}
/>
<YAxis
type="category"
dataKey="name"
width={72}
tick={{ fontSize: 10, fill: 'var(--dg-text-muted)' }}
tickLine={false}
axisLine={false}
/>
<Tooltip
content={(
<MonitorChartTooltip
valueFormatter={(v) => fmtMetricValue(v, '%')}
nameFormatter={() => 'CPU'}
/>
)}
labelFormatter={(_, payload) => {
const item = payload?.[0]?.payload as { fullName?: string } | undefined;
return item?.fullName ?? '';
}}
/>
<Bar dataKey="cpu" radius={[0, 4, 4, 0]} maxBarSize={20} isAnimationActive={false}>
{cpuData.map((entry) => (
<Cell key={entry.fullName} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
)}
</CardContent>
</Card>
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-3">
<PieChartIcon className="h-4 w-4 text-[var(--dg-text-muted)]" />
<h3 className="text-sm font-medium text-[var(--dg-text-primary)]">设备状态</h3>
</div>
<ResponsiveContainer width="100%" height={180}>
<PieChart>
<Pie
data={statusData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
innerRadius={42}
outerRadius={68}
paddingAngle={2}
isAnimationActive={false}
>
{statusData.map((entry) => (
<Cell key={entry.name} fill={entry.fill} stroke={MONITOR_PIE_STROKE} strokeWidth={2} />
))}
</Pie>
<Tooltip content={<MonitorPieTooltip />} />
</PieChart>
</ResponsiveContainer>
<div className="flex flex-wrap justify-center gap-3 mt-1">
{statusData.map((d) => (
<div key={d.name} className="flex items-center gap-1.5 text-xs text-[var(--dg-text-muted)]">
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: d.fill }} />
{d.name} {d.value}
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}