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,141 @@
|
||||
// 中间件概览图表 — 状态 + 命中率 + QPS/连接(支持放大)// Logan.Li
|
||||
import { useMemo, type ReactNode } from 'react';
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, Tooltip,
|
||||
ResponsiveContainer, Cell, PieChart, Pie,
|
||||
} from 'recharts';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { BarChart3, PieChart as PieChartIcon, Percent } from 'lucide-react';
|
||||
import type { MwTarget } from './helpers';
|
||||
import { fmtHitRate, fmtVal } from './helpers';
|
||||
import { ChartExpandShell } from '../shared/ChartExpandShell';
|
||||
import { MonitorChartTooltip, MONITOR_PIE_STROKE, MonitorPieTooltip } from '../shared/monitorChartTheme';
|
||||
|
||||
interface Props {
|
||||
targets: MwTarget[];
|
||||
}
|
||||
|
||||
function shortName(name: string) {
|
||||
return name.length > 10 ? `${name.slice(0, 10)}…` : name;
|
||||
}
|
||||
|
||||
export function MwOverviewCharts({ targets }: Props) {
|
||||
const statusData = useMemo(() => {
|
||||
const online = targets.filter((t) => t.status === 'online').length;
|
||||
const offline = targets.length - online;
|
||||
return [
|
||||
{ name: '在线', value: online, fill: 'var(--dg-green)' },
|
||||
{ name: '离线', value: offline, fill: 'var(--dg-red)' },
|
||||
].filter((d) => d.value > 0);
|
||||
}, [targets]);
|
||||
|
||||
const redisHitData = useMemo(() => targets
|
||||
.filter((t) => t.description === 'redis' && t.latest_metrics?.redis_hit_rate > 0)
|
||||
.map((t) => ({
|
||||
name: shortName(t.name), fullName: t.name,
|
||||
value: (t.latest_metrics.redis_hit_rate || 0) * 100,
|
||||
fill: (t.latest_metrics.redis_hit_rate || 0) >= 0.9 ? 'var(--dg-green)' : 'var(--dg-yellow)',
|
||||
}))
|
||||
.sort((a, b) => a.value - b.value), [targets]);
|
||||
|
||||
const redisQpsData = useMemo(() => targets
|
||||
.filter((t) => t.description === 'redis' && t.latest_metrics?.redis_qps > 0)
|
||||
.map((t) => ({ name: shortName(t.name), fullName: t.name, value: t.latest_metrics.redis_qps, fill: 'var(--dg-blue)' }))
|
||||
.sort((a, b) => b.value - a.value), [targets]);
|
||||
|
||||
const mysqlConnData = useMemo(() => targets
|
||||
.filter((t) => t.description === 'mysql' && t.latest_metrics?.mysql_connections > 0)
|
||||
.map((t) => ({ name: shortName(t.name), fullName: t.name, value: t.latest_metrics.mysql_connections, fill: 'var(--dg-blue)' }))
|
||||
.sort((a, b) => b.value - a.value), [targets]);
|
||||
|
||||
if (targets.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
|
||||
<OverviewChartCard title="实例状态" icon={<PieChartIcon className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={statusData.length === 0}>
|
||||
{(h) => (
|
||||
<ResponsiveContainer width="100%" height={h}>
|
||||
<PieChart>
|
||||
<Pie data={statusData} dataKey="value" nameKey="name" cx="50%" cy="50%"
|
||||
innerRadius={h * 0.22} outerRadius={h * 0.34} paddingAngle={2} isAnimationActive={false}>
|
||||
{statusData.map((e) => <Cell key={e.name} fill={e.fill} stroke={MONITOR_PIE_STROKE} strokeWidth={2} />)}
|
||||
</Pie>
|
||||
<Tooltip content={<MonitorPieTooltip />} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</OverviewChartCard>
|
||||
|
||||
<OverviewChartCard title="Redis 命中率" icon={<Percent className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={redisHitData.length === 0}>
|
||||
{(h) => (
|
||||
<ResponsiveContainer width="100%" height={h}>
|
||||
<BarChart data={redisHitData} layout="vertical" margin={{ left: 0, right: 8 }}>
|
||||
<XAxis type="number" domain={[0, 100]} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickFormatter={(v) => `${v}%`} />
|
||||
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
|
||||
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtHitRate(v / 100)} nameFormatter={() => '命中率'} />} />
|
||||
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
|
||||
{redisHitData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</OverviewChartCard>
|
||||
|
||||
<OverviewChartCard title="Redis QPS" icon={<BarChart3 className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={redisQpsData.length === 0}>
|
||||
{(h) => (
|
||||
<ResponsiveContainer width="100%" height={h}>
|
||||
<BarChart data={redisQpsData} layout="vertical" margin={{ left: 0, right: 8 }}>
|
||||
<XAxis type="number" tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} />
|
||||
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
|
||||
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtVal(v, 1)} nameFormatter={() => 'QPS'} />} />
|
||||
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
|
||||
{redisQpsData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</OverviewChartCard>
|
||||
|
||||
<OverviewChartCard title="MySQL 连接" icon={<BarChart3 className="h-3.5 w-3.5 text-[var(--dg-text-muted)]" />} empty={mysqlConnData.length === 0}>
|
||||
{(h) => (
|
||||
<ResponsiveContainer width="100%" height={h}>
|
||||
<BarChart data={mysqlConnData} layout="vertical" margin={{ left: 0, right: 8 }}>
|
||||
<XAxis type="number" tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} />
|
||||
<YAxis type="category" dataKey="name" width={64} tick={{ fontSize: 9, fill: 'var(--dg-text-muted)' }} tickLine={false} axisLine={false} />
|
||||
<Tooltip content={<MonitorChartTooltip valueFormatter={(v) => fmtVal(v)} nameFormatter={() => '连接'} />} />
|
||||
<Bar dataKey="value" radius={[0, 3, 3, 0]} maxBarSize={14} isAnimationActive={false}>
|
||||
{mysqlConnData.map((e) => <Cell key={e.fullName} fill={e.fill} />)}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</OverviewChartCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewChartCard({
|
||||
title, icon, empty, children,
|
||||
}: {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
empty?: boolean;
|
||||
children: (height: number) => ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-3">
|
||||
<ChartExpandShell
|
||||
title={title}
|
||||
icon={icon}
|
||||
className="bg-transparent p-0"
|
||||
compactHeight={140}
|
||||
expandedHeight={360}
|
||||
empty={empty}
|
||||
>
|
||||
{children}
|
||||
</ChartExpandShell>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user