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

@@ -10,8 +10,12 @@ import {
ArrowRight,
Cloud,
Shield,
Clock3,
Globe,
Database,
Server,
Circle,
Monitor,
Activity,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import type { AlertEventRecord, CertExpiryRecord } from '@/types';
@@ -27,14 +31,6 @@ export interface DashboardRelease {
[key: string]: unknown;
}
export interface DashboardServer {
id: number | string;
status: string;
hostname?: string;
ip?: string;
[key: string]: unknown;
}
export interface DashboardCluster {
id: number | string;
code?: string;
@@ -44,9 +40,34 @@ export interface DashboardCluster {
memory_usage?: number;
node_count?: number;
pod_count?: number;
ingress_count?: number;
deployment_count?: number;
service_count?: number;
[key: string]: unknown;
}
export interface DashboardAutomationStats {
cron_job_count?: number;
script_count?: number;
today_executions?: number;
success_rate?: number;
}
export interface DashboardOpsStats {
domain_count?: number;
site_task_count?: number;
middleware_count?: number;
snmp_device_count?: number;
monitor_target_count?: number;
}
export interface DashboardPipelineStats {
today_count?: number;
success?: number;
failed?: number;
running?: number;
}
/**
* DashboardRecent - 优化后的仪表盘底部卡片组件
*
@@ -336,84 +357,70 @@ export function ReleaseHistoryCard({ releases }: { releases: DashboardRelease[]
);
}
export function ServerStatusCard({ servers }: { servers: DashboardServer[] }) {
/** 运维概览 — 资产 / 监控 / 任务快捷入口 // Logan.Li */
export function OpsOverviewCard({
automation,
ops,
serverCount,
hostMonitorCount,
firingAlerts,
}: {
automation?: DashboardAutomationStats;
ops?: DashboardOpsStats;
serverCount?: number;
hostMonitorCount?: number;
firingAlerts?: number;
}) {
const { t } = useTranslation();
const navigate = useNavigate();
const runningServers = servers.filter((s) => s.status === 'running').length;
const stoppedServers = servers.filter((s) => s.status === 'stopped').length;
const maintenanceServers = servers.filter((s) => s.status === 'maintenance').length;
const tiles = [
{ key: 'servers', label: t('dashboard.ops.servers'), value: serverCount ?? 0, icon: Server, path: '/servers', color: 'text-[var(--dg-blue)]' },
{ key: 'domains', label: t('dashboard.ops.domains'), value: ops?.domain_count ?? 0, icon: Globe, path: '/domains', color: 'text-[var(--dg-cyan)]' },
{ key: 'host', label: t('dashboard.ops.hostMonitor'), value: hostMonitorCount ?? serverCount ?? 0, icon: Monitor, path: '/host-monitor', color: 'text-[var(--dg-green)]' },
{ key: 'mw', label: t('dashboard.ops.middleware'), value: ops?.middleware_count ?? 0, icon: Database, path: '/monitor-center?tab=middleware', color: 'text-[var(--dg-red)]' },
{ key: 'site', label: t('dashboard.ops.siteProbes'), value: ops?.site_task_count ?? 0, icon: Activity, path: '/monitor-center?tab=site', color: 'text-[var(--dg-yellow)]' },
{ key: 'cron', label: t('dashboard.ops.cronJobs'), value: automation?.cron_job_count ?? 0, icon: Clock3, path: '/cronjobs', color: 'text-[var(--dg-purple)]' },
];
return (
<Card className="dg-card dg-card-hover">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-[var(--dg-text-primary)]">{t('dashboard.serversStatus.title')}</CardTitle>
<CardDescription className="text-[var(--dg-text-muted)]">{t('dashboard.serversStatus.description')}</CardDescription>
<CardTitle className="text-[var(--dg-text-primary)]">{t('dashboard.ops.title')}</CardTitle>
<CardDescription className="text-[var(--dg-text-muted)]">{t('dashboard.ops.description')}</CardDescription>
</div>
<Button
variant="ghost"
size="sm"
className="dg-btn-ghost text-xs"
onClick={() => navigate('/servers')}
>
{t('dashboard.serversStatus.viewAll')}
<Button variant="ghost" size="sm" className="dg-btn-ghost text-xs" onClick={() => navigate('/dashboards')}>
{t('dashboard.ops.monitorScreen')}
<ArrowRight className="h-3.5 w-3.5 ml-1" />
</Button>
</div>
</CardHeader>
<CardContent>
{/* Status Summary */}
<div className="grid grid-cols-3 gap-3 mb-4">
<div className="text-center p-3 rounded-xl bg-[var(--dg-green)]/8 border border-[var(--dg-green)]/15">
<div className="text-2xl font-bold text-[var(--dg-green)]">{runningServers}</div>
<div className="text-xs text-[var(--dg-text-muted)] mt-1">{t('dashboard.serversStatus.running')}</div>
</div>
<div className="text-center p-3 rounded-xl bg-[var(--dg-yellow)]/8 border border-[var(--dg-yellow)]/15">
<div className="text-2xl font-bold text-[var(--dg-yellow)]">{maintenanceServers}</div>
<div className="text-xs text-[var(--dg-text-muted)] mt-1">{t('dashboard.serversStatus.maintenance')}</div>
</div>
<div className="text-center p-3 rounded-xl bg-[var(--dg-text-muted)]/8 border border-[var(--dg-border)]">
<div className="text-2xl font-bold text-[var(--dg-text-muted)]">{stoppedServers}</div>
<div className="text-xs text-[var(--dg-text-muted)] mt-1">{t('dashboard.serversStatus.stopped')}</div>
</div>
</div>
{/* Server List */}
<div className="space-y-1.5">
{servers.slice(0, 4).map((server) => (
<div
key={server.id}
className="flex items-center gap-3 p-2.5 rounded-lg bg-[var(--dg-bg-tertiary)]/50 hover:bg-[var(--dg-bg-hover)] transition-all duration-200"
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-3">
{tiles.map((tile) => (
<button
key={tile.key}
type="button"
onClick={() => navigate(tile.path)}
className="flex items-center gap-2.5 p-2.5 rounded-xl bg-[var(--dg-bg-tertiary)]/50 hover:bg-[var(--dg-bg-hover)] transition-all text-left"
>
<Circle
className={cn(
'h-2.5 w-2.5 flex-shrink-0 fill-current',
server.status === 'running' ? 'text-[var(--dg-green)]' :
server.status === 'maintenance' ? 'text-[var(--dg-yellow)]' :
'text-[var(--dg-text-muted)]'
)}
/>
<div className="flex-1 min-w-0">
<p className="text-sm text-[var(--dg-text-primary)] truncate">{server.hostname}</p>
<p className="text-xs text-[var(--dg-text-muted)]">{server.ip}</p>
<tile.icon className={cn('h-4 w-4 shrink-0', tile.color)} />
<div className="min-w-0">
<div className="text-base font-bold text-[var(--dg-text-primary)] leading-none">{tile.value}</div>
<div className="text-[10px] text-[var(--dg-text-muted)] mt-0.5 truncate">{tile.label}</div>
</div>
<div className="text-right flex-shrink-0">
<p className="text-xs text-[var(--dg-text-muted)]">
{t('dashboard.serversStatus.cpu')} <span className="text-[var(--dg-text-secondary)] font-medium">{Number(server.cpu_usage ?? 0).toFixed(1)}%</span>
</p>
<p className="text-xs text-[var(--dg-text-muted)]">
{t('dashboard.serversStatus.memory')} <span className="text-[var(--dg-text-secondary)] font-medium">{Number(server.memory_usage ?? 0).toFixed(1)}%</span>
</p>
</div>
</div>
</button>
))}
{servers.length === 0 && (
<div className="flex flex-col items-center justify-center py-6 text-[var(--dg-text-muted)]">
<Server className="h-8 w-8 mb-2 opacity-30" />
<p className="text-sm">{t('dashboard.serversStatus.empty')}</p>
</div>
)}
</div>
<div className="flex items-center justify-between text-xs px-1 pt-1 border-t border-[var(--dg-border)]">
<span className="text-[var(--dg-text-muted)]">
{t('dashboard.ops.todayExecutions')}: <span className="text-[var(--dg-text-primary)] font-medium">{automation?.today_executions ?? 0}</span>
</span>
<button type="button" className="text-[var(--dg-text-muted)] hover:text-[var(--dg-text-primary)]" onClick={() => navigate('/alert-center')}>
{t('dashboard.ops.firingAlerts')}: <span className={cn('font-medium', (firingAlerts ?? 0) > 0 ? 'text-[var(--dg-red)]' : 'text-[var(--dg-text-primary)]')}>{firingAlerts ?? 0}</span>
</button>
</div>
</CardContent>
</Card>
@@ -454,6 +461,20 @@ export function KubernetesClusterCard({ clusters }: { clusters: DashboardCluster
{clusters.map((cluster) => {
const cpuUsage = Number(cluster.cpu_usage ?? 0);
const memoryUsage = Number(cluster.memory_usage ?? 0);
const hasUsage = cpuUsage > 0 || memoryUsage > 0;
const metrics = hasUsage
? [
{ label: t('dashboard.clusters.nodes'), value: cluster.node_count ?? 0 },
{ label: t('dashboard.clusters.pods'), value: cluster.pod_count ?? 0 },
{ label: t('dashboard.clusters.cpu'), value: `${cpuUsage.toFixed(1)}%` },
{ label: t('dashboard.clusters.memory'), value: `${memoryUsage.toFixed(1)}%` },
]
: [
{ label: t('dashboard.clusters.nodes'), value: cluster.node_count ?? 0 },
{ label: t('dashboard.clusters.pods'), value: cluster.pod_count ?? 0 },
{ label: t('dashboard.clusters.ingress'), value: cluster.ingress_count ?? 0 },
{ label: t('dashboard.clusters.deployments'), value: cluster.deployment_count ?? 0 },
];
return (
<div
key={cluster.id}
@@ -478,12 +499,7 @@ export function KubernetesClusterCard({ clusters }: { clusters: DashboardCluster
</Badge>
</div>
<div className="grid grid-cols-4 gap-2 text-center">
{[
{ label: t('dashboard.clusters.nodes'), value: cluster.node_count ?? 0 },
{ label: t('dashboard.clusters.pods'), value: cluster.pod_count ?? 0 },
{ label: t('dashboard.clusters.cpu'), value: `${cpuUsage.toFixed(1)}%` },
{ label: t('dashboard.clusters.memory'), value: `${memoryUsage.toFixed(1)}%` },
].map((item) => (
{metrics.map((item) => (
<div key={item.label} className="p-1.5 rounded-lg bg-[var(--dg-bg-primary)]/50">
<div className="text-sm font-semibold text-[var(--dg-text-primary)]">{item.value}</div>
<div className="text-[10px] text-[var(--dg-text-muted)] mt-0.5">{item.label}</div>