Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled

This commit is contained in:
2026-10-15 13:53:04 +08:00
commit 2f9f8da48e
3299 changed files with 926841 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
"use strict";
/**
* 巡检报告快照存储
*
* 每次 runReportSnapshot 生成一条快照:
*
* {
* id, // "snap_<ts>_<rand>"
* 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) {
try { return JSON.parse(text); }
catch (e) { 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;
}
module.exports = {
MAX_SNAPSHOTS,
MAX_CMD_STDOUT,
addSnapshot,
listSnapshots,
getSnapshot,
deleteSnapshot,
clearAll,
summarize,
setSnapshotAiStatus,
updateSnapshotAiSummary,
clearSnapshotAiSummary,
};