"use strict"; /** * 巡检报告快照存储 * * 每次 runReportSnapshot 生成一条快照: * * { * id, // "snap__" * timestamp, // 快照生成时间 * duration_ms, // 整体耗时 * trigger, // "manual" | "scheduled" * devices: [ // 每台设备的报告 * { * device_id, * hostname, * catalog_id, * ok, // 设备上所有命令都 ok = true * duration_ms, * commands: [ // 每一条命令的执行结果 * { cmd, purpose, ok, duration_ms, stdout, stderr } * ] * } * ] * } * * 为避免 JSON 无限膨胀,MAX_SNAPSHOTS 条整体保留,每条设备 stdout 截断 MAX_CMD_STDOUT 字符。 */ const { STORAGE_KEY_REPORT_SNAPSHOTS } = require("./storageKeys.cjs"); const MAX_SNAPSHOTS = 200; // 保留最近 200 次快照 const MAX_CMD_STDOUT = 4000; // 单条命令 stdout 保留前 4000 字符(display logbuffer 可能 10k+) let _snapshots = null; function _safeParse(text, def) { if (text === null || text === undefined) return def; if (typeof text === "object") return text; try { return JSON.parse(text); } catch { return def; } } function _ensureLoaded(readFn) { if (_snapshots === null) { const raw = readFn(STORAGE_KEY_REPORT_SNAPSHOTS); _snapshots = raw ? _safeParse(raw, []) : []; } } function _save(writeFn) { if (_snapshots.length > MAX_SNAPSHOTS) { _snapshots = _snapshots.slice(-MAX_SNAPSHOTS); } writeFn(STORAGE_KEY_REPORT_SNAPSHOTS, JSON.stringify(_snapshots)); } function _truncate(text, max = MAX_CMD_STDOUT) { if (!text) return ""; if (text.length <= max) return text; return text.slice(0, max) + `\n... [truncated ${text.length - max} chars]`; } // ---------- 增删改查 ---------- /** * 新增一条快照。snapshot.devices[].commands[].stdout 会自动截断。 */ function addSnapshot(readFn, writeFn, snapshot) { _ensureLoaded(readFn); // 自动截断 stdout const cleanDevices = (snapshot.devices || []).map(d => ({ ...d, commands: (d.commands || []).map(c => ({ ...c, stdout: _truncate(c.stdout || ""), stderr: _truncate(c.stderr || "", 500), })), })); const record = { id: snapshot.id || `snap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, timestamp: snapshot.timestamp || Date.now(), duration_ms: snapshot.duration_ms || 0, trigger: snapshot.trigger || "manual", devices: cleanDevices, }; _snapshots.push(record); _save(writeFn); return { ...record }; } /** * 列出快照(新的在前) */ function listSnapshots(readFn, limit = 50) { _ensureLoaded(readFn); return [..._snapshots].reverse().slice(0, limit); } /** * 按 id 查单条快照 */ function getSnapshot(readFn, id) { _ensureLoaded(readFn); return _snapshots.find(s => s.id === id) || null; } /** * 删除一条快照 */ function deleteSnapshot(readFn, writeFn, id) { _ensureLoaded(readFn); const before = _snapshots.length; _snapshots = _snapshots.filter(s => s.id !== id); if (_snapshots.length < before) { _save(writeFn); return true; } return false; } /** * 清空所有快照 */ function clearAll(readFn, writeFn) { _ensureLoaded(readFn); _snapshots = []; _save(writeFn); } /** * 统计概览(用于日运行报告聚合) */ function summarize(readFn, sinceTs) { _ensureLoaded(readFn); const snaps = _snapshots.filter(s => s.timestamp >= sinceTs); let totalDevices = 0; let totalCmds = 0; let okCmds = 0; let failedCmds = 0; const failedDevices = new Set(); for (const snap of snaps) { for (const dev of snap.devices || []) { totalDevices++; for (const cmd of dev.commands || []) { totalCmds++; if (cmd.ok) okCmds++; else { failedCmds++; failedDevices.add(dev.device_id); } } } } return { snapshot_count: snaps.length, total_device_checks: totalDevices, total_commands: totalCmds, ok_commands: okCmds, failed_commands: failedCmds, failed_device_ids: Array.from(failedDevices), }; } /** * 标记快照 AI 分析状态(running 主进程在跑、done 成功、failed 失败) */ function setSnapshotAiStatus(readFn, writeFn, snapshotId, status, error) { _ensureLoaded(readFn); const snap = _snapshots.find(s => s.id === snapshotId); if (!snap) return false; snap.ai_summary_status = status; // "running" | "done" | "failed" | undefined if (error) snap.ai_summary_error = error; else delete snap.ai_summary_error; if (status === "running") { delete snap.ai_summary; delete snap.ai_summary_updated_at; } _save(writeFn); return true; } /** * 更新快照的 AI 汇总(AI 成功分析后存回,status 自动设为 done) */ function updateSnapshotAiSummary(readFn, writeFn, snapshotId, aiSummary, error) { _ensureLoaded(readFn); const snap = _snapshots.find(s => s.id === snapshotId); if (!snap) return false; if (error) { snap.ai_summary_status = "failed"; snap.ai_summary_error = error; delete snap.ai_summary; } else { snap.ai_summary_status = "done"; snap.ai_summary = aiSummary; snap.ai_summary_updated_at = Date.now(); delete snap.ai_summary_error; } _save(writeFn); return true; } /** * 清空快照的 AI 汇总(用于重试前清除旧结果) */ function clearSnapshotAiSummary(readFn, writeFn, snapshotId) { _ensureLoaded(readFn); const snap = _snapshots.find(s => s.id === snapshotId); if (!snap) return false; delete snap.ai_summary; delete snap.ai_summary_updated_at; _save(writeFn); return true; } function _resetForTest() { _snapshots = null; } module.exports = { MAX_SNAPSHOTS, MAX_CMD_STDOUT, addSnapshot, listSnapshots, getSnapshot, deleteSnapshot, clearAll, summarize, setSnapshotAiStatus, updateSnapshotAiSummary, clearSnapshotAiSummary, _resetForTest, };