feat: add monitoring settings tab, workbench view, and inspection alert improvements
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-30 12:12:04 +08:00
committed by trae-solo-agent
parent 16dd04f746
commit 4a02370006
26 changed files with 2569 additions and 233 deletions

View File

@@ -90,6 +90,17 @@ function broadcastRuleChanged() {
} catch (_) {}
}
// 相机配置广播(Settings 改了 → 所有 window 里的监控 view 收到)
function broadcastCameraConfigUpdated(cfg) {
try {
const { webContents } = require("electron");
for (const wc of webContents.getAllWebContents()) {
wc.send("inspection:camera-config-updated", cfg);
}
console.info("[inspection] broadcastCameraConfigUpdated sent");
} catch (_) {}
}
// AI 汇总状态广播(主进程主动推送 → renderer loadSnapshots 刷新)
function _broadcastReportAiStatus(snapshotId, status, error) {
try {
@@ -504,6 +515,10 @@ function registerInspectionHandlers(ipcMain, options) {
return inspectionResultStore.getRecent(readFn, limit);
});
ipcMain.handle("inspection:getResultsByTimeRange", (_e, startTime, endTime) => {
return inspectionResultStore.getByTimeRange(readFn, startTime, endTime);
});
// ---------- 单次巡检执行(前端传入完整主机配置) ----------
ipcMain.handle("inspection:runInspection", async (_e, payload) => {
const { hostId, hostConfig, catalogId } = payload || {};
@@ -1707,6 +1722,168 @@ function registerInspectionHandlers(ipcMain, options) {
}
});
// ---------- 监控大屏地图底图持久化 ----------
const _mapsDir = options.userDataDir && options.path
? options.path.join(options.userDataDir, "monitoring", "maps")
: null;
function _ensureMapsDir() {
if (!_mapsDir || !options.fs) throw new Error("maps dir not available");
if (!options.fs.existsSync(_mapsDir)) {
options.fs.mkdirSync(_mapsDir, { recursive: true });
}
}
const _ALLOWED_EXTS = new Set([".png", ".jpg", ".jpeg", ".webp"]);
const _MAX_BG_BYTES = 20 * 1024 * 1024; // 20MB
function _sanitizeBgFilename(raw) {
if (typeof raw !== "string" || !raw.trim()) return "";
// 只保留安全字符 + 扩展名
const base = raw.replace(/[\\/]/g, "_").replace(/[^\w.\-\u4e00-\u9fff]/g, "");
return base.length > 0 ? base : "map";
}
ipcMain.handle("inspection:uploadMapBg", (_e, { filename, base64Data }) => {
try {
if (!_mapsDir) return { ok: false, error: "maps dir not available" };
if (typeof filename !== "string" || !filename) return { ok: false, error: "filename required" };
if (typeof base64Data !== "string" || !base64Data) return { ok: false, error: "base64Data required" };
// 类型校验
const ext = options.path.extname(filename).toLowerCase();
if (!_ALLOWED_EXTS.has(ext)) {
return { ok: false, error: "unsupported type, allowed: png / jpg / jpeg / webp" };
}
// 大小校验
const buf = Buffer.from(base64Data, "base64");
if (buf.length > _MAX_BG_BYTES) {
return { ok: false, error: "file too large, max 20MB" };
}
_ensureMapsDir();
const safeName = _sanitizeBgFilename(filename);
const ts = Date.now();
const storedName = `${ts}_${safeName}`;
const target = options.path.join(_mapsDir, storedName);
options.fs.writeFileSync(target, buf);
console.info("[inspection] uploadMapBg ok:", storedName, "size:", buf.length);
return { ok: true, filename: storedName, size: buf.length };
} catch (e) {
console.error("[inspection] uploadMapBg error:", e?.message);
return { ok: false, error: e?.message };
}
});
ipcMain.handle("inspection:listMapBgs", () => {
try {
if (!_mapsDir || !options.fs) return [];
if (!options.fs.existsSync(_mapsDir)) return [];
const all = options.fs.readdirSync(_mapsDir);
console.info("[inspection] listMapBgs dir:", _mapsDir, "raw entries:", all);
const entries = all.filter((f) => {
const ext = options.path.extname(f).toLowerCase();
const ok = _ALLOWED_EXTS.has(ext);
console.info("[inspection] listMapBgs filter:", f, "ext:", ext, "ok:", ok);
return ok;
});
return entries
.map((name) => {
const stat = options.fs.statSync(options.path.join(_mapsDir, name));
return { filename: name, size: stat.size, mtimeMs: stat.mtimeMs };
})
.sort((a, b) => b.mtimeMs - a.mtimeMs);
} catch (e) {
console.error("[inspection] listMapBgs error:", e?.message);
return [];
}
});
ipcMain.handle("inspection:selectMapBg", (_e, filename) => {
try {
if (typeof filename !== "string" || !filename) return { ok: false, error: "filename required" };
// 校验文件实际存在
if (_mapsDir && options.fs) {
const fullPath = options.path.join(_mapsDir, filename);
if (!options.fs.existsSync(fullPath)) {
return { ok: false, error: "file not found" };
}
}
writeStorage("_map_bg_selected", filename);
return { ok: true, filename };
} catch (e) {
return { ok: false, error: e?.message };
}
});
ipcMain.handle("inspection:getMapBgData", () => {
try {
const selected = readStorage("_map_bg_selected");
if (!selected || typeof selected !== "string") {
return { ok: false, reason: "none" };
}
if (!_mapsDir || !options.fs) return { ok: false, reason: "no-dir" };
const fullPath = options.path.join(_mapsDir, selected);
if (!options.fs.existsSync(fullPath)) {
// 选中的文件已被删除 → 清理 selected
writeStorage("_map_bg_selected", null);
return { ok: false, reason: "missing" };
}
const buf = options.fs.readFileSync(fullPath);
const ext = options.path.extname(selected).toLowerCase();
const mimeMap = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
const mime = mimeMap[ext] || "image/png";
const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
return { ok: true, filename: selected, dataUrl, size: buf.length };
} catch (e) {
console.error("[inspection] getMapBgData error:", e?.message);
return { ok: false, error: e?.message };
}
});
ipcMain.handle("inspection:deleteMapBg", (_e, filename) => {
try {
if (!_mapsDir || !options.fs) return { ok: false, error: "maps dir not available" };
const fullPath = options.path.join(_mapsDir, filename);
if (options.fs.existsSync(fullPath)) {
options.fs.unlinkSync(fullPath);
}
// 如果删的是当前选中的 → 清空 selected
const selected = readStorage("_map_bg_selected");
if (selected === filename) {
writeStorage("_map_bg_selected", null);
}
return { ok: true };
} catch (e) {
return { ok: false, error: e?.message };
}
});
ipcMain.handle("inspection:resetMapBg", () => {
try {
writeStorage("_map_bg_selected", null);
return { ok: true };
} catch (e) {
return { ok: false, error: e?.message };
}
});
// 相机配置:Settings window 发过来,主进程存 localStorage + 广播给所有 window
ipcMain.handle("inspection:setCameraConfig", (_e, cfg) => {
try {
if (!cfg || typeof cfg !== "object") return { ok: false, error: "cfg required" };
// 主进程侧也存一份 localStorage(和渲染进程隔离的 session)
// 但渲染进程各 window 共享同一个 session,所以写一遍即可
broadcastCameraConfigUpdated(cfg);
return { ok: true };
} catch (e) {
console.error("[inspection] setCameraConfig error:", e?.message);
return { ok: false, error: e?.message };
}
});
ipcMain.handle("inspection:getDashboardTitle", () => {
try {
return readStorage("_dashboard_title") || "";