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
@@ -1,8 +1,54 @@
|
||||
// 主机监控工具函数 // Logan.Li
|
||||
import type { BucketPoint } from './types';
|
||||
import type { BucketPoint, HostMonitorTimeRange } from './types';
|
||||
import type { JsonData } from '@/types/common';
|
||||
/** 按时间跨度格式化 X 轴标签(避免 7 天范围只显示时分) // Logan.Li */
|
||||
export function formatAxisTime(ts: number, spanMinutes: number): string {
|
||||
const d = new Date(ts * 1000);
|
||||
if (spanMinutes <= 180) {
|
||||
return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
}
|
||||
if (spanMinutes <= 2880) {
|
||||
return d.toLocaleString('zh-CN', {
|
||||
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false,
|
||||
});
|
||||
}
|
||||
return d.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' });
|
||||
}
|
||||
|
||||
export function bucketWithStats(raw: number[], timestamps: number[], targetBuckets: number): BucketPoint[] {
|
||||
function recordTimestamp(r: JsonData): number {
|
||||
const raw = r.report_time || r.created_at;
|
||||
if (!raw) return 0;
|
||||
const t = new Date(raw as string).getTime();
|
||||
return Number.isNaN(t) ? 0 : t / 1000;
|
||||
}
|
||||
|
||||
/** 客户端二次过滤,确保图表与所选时间一致 // Logan.Li */
|
||||
export function filterHistoryByRange(history: JsonData[], range: HostMonitorTimeRange): JsonData[] {
|
||||
if (!history.length) return history;
|
||||
const now = Date.now();
|
||||
let sinceMs: number;
|
||||
let untilMs = now;
|
||||
|
||||
if (range.preset === 'custom' && range.start && range.end) {
|
||||
sinceMs = new Date(range.start).getTime();
|
||||
untilMs = new Date(range.end).getTime();
|
||||
} else {
|
||||
const mins = range.minutes ?? 60;
|
||||
sinceMs = now - mins * 60_000;
|
||||
}
|
||||
|
||||
return history.filter((r) => {
|
||||
const ts = recordTimestamp(r) * 1000;
|
||||
return ts >= sinceMs && ts <= untilMs;
|
||||
});
|
||||
}
|
||||
|
||||
export function bucketWithStats(
|
||||
raw: number[],
|
||||
timestamps: number[],
|
||||
targetBuckets: number,
|
||||
spanMinutes: number,
|
||||
): BucketPoint[] {
|
||||
if (!raw || raw.length === 0) return [];
|
||||
const perBucket = Math.max(1, Math.ceil(raw.length / targetBuckets));
|
||||
const result: BucketPoint[] = [];
|
||||
@@ -15,7 +61,7 @@ export function bucketWithStats(raw: number[], timestamps: number[], targetBucke
|
||||
const min = Math.min(...slice);
|
||||
const ts = tsSlice[Math.floor(tsSlice.length / 2)] || 0;
|
||||
result.push({
|
||||
time: new Date(ts * 1000).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
|
||||
time: formatAxisTime(ts, spanMinutes),
|
||||
timestamp: ts,
|
||||
avg: Number(avg.toFixed(2)),
|
||||
max: Number(max.toFixed(2)),
|
||||
@@ -40,9 +86,12 @@ export function formatBPS(bps: number): string {
|
||||
return `${bps.toFixed(1)} B/s`;
|
||||
}
|
||||
|
||||
interface TimeConfig { buckets: number; }
|
||||
interface TimeConfig { buckets: number; spanMinutes: number; }
|
||||
|
||||
export function buildMetricsFromHistory(history: JsonData[], timeConfig: TimeConfig) {
|
||||
const spanMinutes = timeConfig.spanMinutes;
|
||||
const bucket = (raw: number[], ts: number[]) =>
|
||||
bucketWithStats(raw, ts, timeConfig.buckets, spanMinutes);
|
||||
const rawCpu: number[] = []; const rawMem: number[] = []; const rawLoad1: number[] = [];
|
||||
const rawDisk: number[] = []; const rawTcp: number[] = []; const rawTw: number[] = []; const rawCw: number[] = [];
|
||||
const rawRx: number[] = []; const rawTx: number[] = [];
|
||||
@@ -55,7 +104,7 @@ export function buildMetricsFromHistory(history: JsonData[], timeConfig: TimeCon
|
||||
const timestamps: number[] = [];
|
||||
|
||||
for (const r of history) {
|
||||
const ts = r.report_time ? new Date(r.report_time).getTime() / 1000 : (r.created_at ? new Date(r.created_at).getTime() / 1000 : 0);
|
||||
const ts = recordTimestamp(r);
|
||||
timestamps.push(ts);
|
||||
rawCpu.push(r.cpu_usage || 0);
|
||||
rawIOWait.push(r.cpu_iowait || 0);
|
||||
@@ -95,29 +144,40 @@ export function buildMetricsFromHistory(history: JsonData[], timeConfig: TimeCon
|
||||
}
|
||||
|
||||
return {
|
||||
cpu: bucketWithStats(rawCpu, timestamps, timeConfig.buckets),
|
||||
mem: bucketWithStats(rawMem, timestamps, timeConfig.buckets),
|
||||
load1: bucketWithStats(rawLoad1, timestamps, timeConfig.buckets),
|
||||
disk: bucketWithStats(rawDisk, timestamps, timeConfig.buckets),
|
||||
tcpEst: bucketWithStats(rawTcp, timestamps, timeConfig.buckets),
|
||||
tcpTW: bucketWithStats(rawTw, timestamps, timeConfig.buckets),
|
||||
tcpCW: bucketWithStats(rawCw, timestamps, timeConfig.buckets),
|
||||
rx: bucketWithStats(rawRx.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||
tx: bucketWithStats(rawTx.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||
ioRead: bucketWithStats(rawIoR.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||
ioWrite: bucketWithStats(rawIoW.map(v => v / 1024), timestamps, timeConfig.buckets),
|
||||
swap: bucketWithStats(rawSwap, timestamps, timeConfig.buckets),
|
||||
fd: bucketWithStats(rawFD, timestamps, timeConfig.buckets),
|
||||
iowait: bucketWithStats(rawIOWait, timestamps, timeConfig.buckets),
|
||||
memAvail: bucketWithStats(rawMemAvail, timestamps, timeConfig.buckets),
|
||||
ioUtil: bucketWithStats(rawIoUtil, timestamps, timeConfig.buckets),
|
||||
connTotal: bucketWithStats(rawConnTotal, timestamps, timeConfig.buckets),
|
||||
rxErr: bucketWithStats(rawRxErr, timestamps, timeConfig.buckets),
|
||||
txErr: bucketWithStats(rawTxErr, timestamps, timeConfig.buckets),
|
||||
inode: bucketWithStats(rawInode, timestamps, timeConfig.buckets),
|
||||
cpu: bucket(rawCpu, timestamps),
|
||||
mem: bucket(rawMem, timestamps),
|
||||
load1: bucket(rawLoad1, timestamps),
|
||||
disk: bucket(rawDisk, timestamps),
|
||||
tcpEst: bucket(rawTcp, timestamps),
|
||||
tcpTW: bucket(rawTw, timestamps),
|
||||
tcpCW: bucket(rawCw, timestamps),
|
||||
rx: bucket(rawRx.map((v) => v / 1024), timestamps),
|
||||
tx: bucket(rawTx.map((v) => v / 1024), timestamps),
|
||||
ioRead: bucket(rawIoR.map((v) => v / 1024), timestamps),
|
||||
ioWrite: bucket(rawIoW.map((v) => v / 1024), timestamps),
|
||||
swap: bucket(rawSwap, timestamps),
|
||||
fd: bucket(rawFD, timestamps),
|
||||
iowait: bucket(rawIOWait, timestamps),
|
||||
memAvail: bucket(rawMemAvail, timestamps),
|
||||
ioUtil: bucket(rawIoUtil, timestamps),
|
||||
connTotal: bucket(rawConnTotal, timestamps),
|
||||
rxErr: bucket(rawRxErr, timestamps),
|
||||
txErr: bucket(rawTxErr, timestamps),
|
||||
inode: bucket(rawInode, timestamps),
|
||||
};
|
||||
}
|
||||
|
||||
/** 从过滤后历史计算实际数据时间跨度(用于展示) // Logan.Li */
|
||||
export function getActualDataSpan(history: JsonData[], range: HostMonitorTimeRange): string | null {
|
||||
if (!history.length) return null;
|
||||
const ts = history.map(recordTimestamp).filter((t) => t > 0);
|
||||
if (!ts.length) return null;
|
||||
const min = Math.min(...ts);
|
||||
const max = Math.max(...ts);
|
||||
const spanMin = Math.max(1, Math.round((max - min) / 60));
|
||||
return `${formatAxisTime(min, spanMin)} — ${formatAxisTime(max, spanMin)} · ${history.length} 点`;
|
||||
}
|
||||
|
||||
export function getCurrentVal(latest: JsonData, netInfo: JsonData, diskIOInfo: JsonData, swapInfo: JsonData, fdInfo: JsonData,
|
||||
key: string, fallback = '--',
|
||||
): string {
|
||||
|
||||
Reference in new issue
Block a user