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,126 @@
|
||||
// 主机监控时间范围选择器 — 预设 + 自定义 // Logan.Li
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { Calendar, ChevronDown, Timer } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
TIME_RANGE_PRESETS,
|
||||
type HostMonitorTimeRange,
|
||||
type TimeRangePresetId,
|
||||
} from './types';
|
||||
import { presetToRange, toDatetimeLocalValue, formatRangeSummary } from './timeRange';
|
||||
|
||||
interface Props {
|
||||
value: HostMonitorTimeRange;
|
||||
onChange: (range: HostMonitorTimeRange) => void;
|
||||
}
|
||||
|
||||
export function HostTimeRangePicker({ value, onChange }: Props) {
|
||||
const [customOpen, setCustomOpen] = useState(false);
|
||||
const now = new Date();
|
||||
const [customStart, setCustomStart] = useState(toDatetimeLocalValue(new Date(now.getTime() - 3600_000)));
|
||||
const [customEnd, setCustomEnd] = useState(toDatetimeLocalValue(now));
|
||||
const popRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (popRef.current && !popRef.current.contains(e.target as Node)) setCustomOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
}, []);
|
||||
|
||||
const selectPreset = (id: TimeRangePresetId) => {
|
||||
if (id === 'custom') return;
|
||||
onChange(presetToRange(id));
|
||||
setCustomOpen(false);
|
||||
};
|
||||
|
||||
const applyCustom = () => {
|
||||
const start = new Date(customStart);
|
||||
const end = new Date(customEnd);
|
||||
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return;
|
||||
const s = start.getTime() <= end.getTime() ? start : end;
|
||||
const e = start.getTime() <= end.getTime() ? end : start;
|
||||
onChange({
|
||||
preset: 'custom',
|
||||
label: '自定义',
|
||||
start: s.toISOString(),
|
||||
end: e.toISOString(),
|
||||
buckets: Math.min(120, Math.max(36, Math.ceil((e.getTime() - s.getTime()) / 60_000 / 2))),
|
||||
});
|
||||
setCustomOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-[11px] dg-text-muted hidden sm:inline-flex items-center gap-1">
|
||||
<Timer className="h-3 w-3" />
|
||||
时间范围
|
||||
</span>
|
||||
<div className="flex items-center gap-1 rounded-md border border-[var(--dg-border)] p-0.5 bg-[var(--dg-bg-secondary)] flex-wrap">
|
||||
{TIME_RANGE_PRESETS.map((p) => (
|
||||
<Button
|
||||
key={p.id}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`h-6 text-[11px] px-2 rounded ${value.preset === p.id ? 'dg-btn-primary shadow-sm' : 'dg-text-secondary hover:dg-text-primary'}`}
|
||||
onClick={() => selectPreset(p.id)}
|
||||
>
|
||||
{p.label}
|
||||
</Button>
|
||||
))}
|
||||
<div className="relative" ref={popRef}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`h-6 text-[11px] px-2 rounded gap-1 ${value.preset === 'custom' ? 'dg-btn-primary shadow-sm' : 'dg-text-secondary hover:dg-text-primary'}`}
|
||||
onClick={() => setCustomOpen((v) => !v)}
|
||||
>
|
||||
<Calendar className="h-3 w-3" />
|
||||
自定义
|
||||
<ChevronDown className={`h-3 w-3 transition-transform ${customOpen ? 'rotate-180' : ''}`} />
|
||||
</Button>
|
||||
{customOpen && (
|
||||
<div className="absolute right-0 top-full mt-1 z-50 w-72 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-primary)] p-3 shadow-lg">
|
||||
<p className="text-xs font-medium text-[var(--dg-text-primary)] mb-2">自定义时间范围</p>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="text-[10px] dg-text-muted">开始</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={customStart}
|
||||
onChange={(e) => setCustomStart(e.target.value)}
|
||||
className="h-8 text-xs dg-input mt-0.5"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] dg-text-muted">结束</label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={customEnd}
|
||||
onChange={(e) => setCustomEnd(e.target.value)}
|
||||
className="h-8 text-xs dg-input mt-0.5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-3">
|
||||
<Button type="button" variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setCustomOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="button" size="sm" className="h-7 text-xs dg-btn-primary" onClick={applyCustom}>
|
||||
应用
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-[10px] dg-text-muted tabular-nums hidden md:inline">
|
||||
{formatRangeSummary(value)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user