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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user