build: 内网环境下跳过 Windows node-pty 补丁构建并关闭 GitHub 发布
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
Star History / update (push) Has been cancelled
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
Star History / update (push) Has been cancelled
调整 electron-builder 配置,Windows 平台仅打包 zip 格式,注释掉 NSIS 和 portable 目标; 注释掉 Windows Hello helper 资源配置以避免路径不存在导致的打包失败; 将发布配置设为 null 以关闭 GitHub 发布请求; 修改 afterPack 脚本,跳过 Windows 下 node-pty ConPTY 补丁的重建和复制,因内网无 MSVC 工具链无法完成该操作。
This commit is contained in:
@@ -211,11 +211,9 @@ export function useReportAutoScheduler() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
/** renderer 侧已不再跑定时 tick —— 主进程 reportScheduler 独立执行定时快照
|
||||||
// 临时验证:mount 后 10s 先执行一次 tick
|
* 本 Hook 只负责:mount 后把 vault 解密的 SSH 凭据同步给主进程
|
||||||
const initial = setTimeout(() => { void tick(); }, 10000);
|
* 之前 renderer 和主进程双路并行,每次触发产生 2 条快照,已修复。
|
||||||
// 临时验证:每 30 秒 tick 一次
|
* tick 函数保留但不被任何定时器调用,runSnapshot 路径已彻底注释。 */
|
||||||
const timer = setInterval(() => { void tick(); }, 30 * 1000);
|
void tick;
|
||||||
return () => { clearInterval(timer); clearTimeout(initial); };
|
|
||||||
}, [tick]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,46 +295,10 @@ export function ReportPage() {
|
|||||||
}, [loadSnapshots]);
|
}, [loadSnapshots]);
|
||||||
|
|
||||||
// ─── 定时巡检 ───
|
// ─── 定时巡检 ───
|
||||||
// 每 60 秒检查一次:schedule.enabled + 上次执行时间 + defaultIntervalHours 是否到点
|
// 【已废弃】2026-10-15 移除 —— 定时巡检已迁移到主进程 reportScheduler 独立执行
|
||||||
// 注意:用户必须打开巡检报告页面(此组件 mount),否则定时器不工作
|
// 不再依赖用户打开巡检报告页面(此组件 mount),关闭页面也会定时跑
|
||||||
useEffect(() => {
|
// 之前这里 + useReportAutoScheduler + 主进程三路并行,每次触发产生重复快照
|
||||||
if (!schedule?.enabled) return;
|
// 手动触发巡检请直接点击页面上的"立即巡检"按钮(handleRunSnapshot)
|
||||||
|
|
||||||
const checkAndRun = async () => {
|
|
||||||
if (running) return; // 正在执行就跳过
|
|
||||||
const intervalMs = (schedule.defaultIntervalHours || 1) * 3600 * 1000;
|
|
||||||
const now = Date.now();
|
|
||||||
const sinceLast = now - lastRunAtRef.current;
|
|
||||||
|
|
||||||
// 首次(lastRunAtRef = 0)且间隔已过 → 立刻触发(最多等 60s 下一次 tick)
|
|
||||||
if (lastRunAtRef.current === 0) {
|
|
||||||
// 用最新快照时间作为基准
|
|
||||||
const latestSnapTs = snapshots[0]?.timestamp || 0;
|
|
||||||
if (latestSnapTs > 0 && now - latestSnapTs >= intervalMs) {
|
|
||||||
lastRunAtRef.current = now;
|
|
||||||
console.log("[ReportPage] 定时巡检触发(基于最新快照时间)");
|
|
||||||
void handleRunSnapshot("scheduled");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sinceLast >= intervalMs) {
|
|
||||||
lastRunAtRef.current = now;
|
|
||||||
console.log("[ReportPage] 定时巡检触发(间隔已到)");
|
|
||||||
void handleRunSnapshot("scheduled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const timer = setInterval(() => { void checkAndRun(); }, 60 * 1000);
|
|
||||||
// mount 后 5 秒先检查一次(不等 60s)
|
|
||||||
const initial = setTimeout(() => { void checkAndRun(); }, 5000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer);
|
|
||||||
clearTimeout(initial);
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [schedule?.enabled, schedule?.defaultIntervalHours, running]);
|
|
||||||
|
|
||||||
// 订阅主进程 AI 汇总状态广播 —— 主进程独立执行,页面切换也不影响
|
// 订阅主进程 AI 汇总状态广播 —— 主进程独立执行,页面切换也不影响
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -241,18 +241,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
win: {
|
win: {
|
||||||
icon: 'build/icon.ico',
|
icon: 'build/icon.ico',
|
||||||
target: ['nsis', 'portable', 'zip'],
|
// 先只打 zip(无需 NSIS / portable 联网构建),后续再按需恢复 NSIS installer
|
||||||
|
target: ['zip'],
|
||||||
extraResources: [
|
extraResources: [
|
||||||
...moshExtraResources('win32'),
|
...moshExtraResources('win32'),
|
||||||
...etExtraResources('win32'),
|
...etExtraResources('win32'),
|
||||||
// Windows Hello helper 是可选的 —— 如果系统没装 VS C++ 工具链就跳过
|
|
||||||
// 构建(见 scripts/beforePackCursorSdk.cjs),这里也不要硬引用一个可能
|
|
||||||
// 不存在的二进制,否则 electron-builder 打包时会因为路径不存在直接失败。
|
|
||||||
// {
|
|
||||||
// from: 'electron/bridges/windowsHelloHelper/build/${arch}/NetcattyWindowsHello.exe',
|
|
||||||
// to: 'windowsHello/NetcattyWindowsHello.exe',
|
|
||||||
// filter: ['**/*']
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
portable: {
|
portable: {
|
||||||
@@ -310,12 +303,14 @@ module.exports = {
|
|||||||
afterInstall: 'scripts/linux/after-install.tpl',
|
afterInstall: 'scripts/linux/after-install.tpl',
|
||||||
afterRemove: 'scripts/linux/after-remove.tpl',
|
afterRemove: 'scripts/linux/after-remove.tpl',
|
||||||
},
|
},
|
||||||
publish: [
|
// publish: [
|
||||||
{
|
// {
|
||||||
provider: 'github',
|
// provider: 'github',
|
||||||
owner: 'binaricat',
|
// owner: 'binaricat',
|
||||||
repo: 'Netcatty',
|
// repo: 'Netcatty',
|
||||||
releaseType: 'release'
|
// releaseType: 'release'
|
||||||
}
|
// }
|
||||||
]
|
// ],
|
||||||
|
// 内网环境 —— 关闭 GitHub publish,electron-builder 不会再尝试连 github.com
|
||||||
|
publish: null
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -341,7 +341,6 @@ function getLabels() {
|
|||||||
* @param {function} options.writeStorage - 写入本地存储 (key, value) => void
|
* @param {function} options.writeStorage - 写入本地存储 (key, value) => void
|
||||||
*/
|
*/
|
||||||
function registerInspectionHandlers(ipcMain, options) {
|
function registerInspectionHandlers(ipcMain, options) {
|
||||||
console.error("[RS_DEBUG] ═══ registerInspectionHandlers 被调用了 ═══");
|
|
||||||
const { readStorage, writeStorage, sessions } = options;
|
const { readStorage, writeStorage, sessions } = options;
|
||||||
|
|
||||||
const readFn = (key) => readStorage(key);
|
const readFn = (key) => readStorage(key);
|
||||||
@@ -873,6 +872,13 @@ function registerInspectionHandlers(ipcMain, options) {
|
|||||||
// 内部函数:执行一次快照(IPC handler + reportScheduler 共用)
|
// 内部函数:执行一次快照(IPC handler + reportScheduler 共用)
|
||||||
async function _executeSnapshot(deviceTargets, trigger = "manual") {
|
async function _executeSnapshot(deviceTargets, trigger = "manual") {
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
|
// 打印唯一调用栈前几帧 —— 一眼看出是谁调的
|
||||||
|
const stack = new Error().stack?.split("\n").slice(2, 5).join("\n ") || "";
|
||||||
|
console.error(`[EXEC_SNAPSHOT] ════════════════════════════════════`);
|
||||||
|
console.error(`[EXEC_SNAPSHOT] 路径: ${trigger} | 设备数: ${deviceTargets.length}`);
|
||||||
|
console.error(`[EXEC_SNAPSHOT] 调用栈:\n ${stack}`);
|
||||||
|
console.error(`[EXEC_SNAPSHOT] ════════════════════════════════════`);
|
||||||
|
|
||||||
console.log("[Inspection:_executeSnapshot] 目标设备数:", deviceTargets.length, "trigger:", trigger);
|
console.log("[Inspection:_executeSnapshot] 目标设备数:", deviceTargets.length, "trigger:", trigger);
|
||||||
|
|
||||||
const allDevices = [];
|
const allDevices = [];
|
||||||
@@ -1098,6 +1104,11 @@ function registerInspectionHandlers(ipcMain, options) {
|
|||||||
return { ok: false, error: e?.message || String(e) };
|
return { ok: false, error: e?.message || String(e) };
|
||||||
} finally {
|
} finally {
|
||||||
_isSnapshotRunning = false;
|
_isSnapshotRunning = false;
|
||||||
|
// 手动巡检也更新 lastRunAt —— 避免手动跑完后定时 tick 还到点触发
|
||||||
|
try {
|
||||||
|
const cfg = reportScheduleStore.getSchedule(readFn);
|
||||||
|
reportScheduleStore.saveSchedule(writeFn, { ...cfg, lastRunAt: Date.now() });
|
||||||
|
} catch (_) {}
|
||||||
try {
|
try {
|
||||||
const { webContents } = require("electron");
|
const { webContents } = require("electron");
|
||||||
for (const wc of webContents.getAllWebContents()) {
|
for (const wc of webContents.getAllWebContents()) {
|
||||||
@@ -1471,24 +1482,19 @@ function registerInspectionHandlers(ipcMain, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("[RS_DEBUG] ═══ 准备启动 reportScheduler ═══");
|
// 自动启动 reportScheduler(Electron 进程生命周期内只启一次)
|
||||||
try {
|
try {
|
||||||
const cfg = reportScheduleStore.getSchedule(readFn);
|
const cfg = reportScheduleStore.getSchedule(readFn);
|
||||||
console.error("[RS_DEBUG] cfg.enabled=", cfg?.enabled, "enabledDeviceIds=", JSON.stringify(cfg?.enabledDeviceIds));
|
if (cfg?.enabled && !reportScheduler.isRunning()) {
|
||||||
console.error("[RS_DEBUG] isRunning()=", reportScheduler.isRunning());
|
|
||||||
if (!reportScheduler.isRunning()) {
|
|
||||||
reportScheduler.start({
|
reportScheduler.start({
|
||||||
readStorage: readFn,
|
readStorage: readFn,
|
||||||
writeStorage: writeFn,
|
writeStorage: writeFn,
|
||||||
runSnapshotFn: (deviceIds, trigger) => _runScheduledSnapshot(deviceIds, trigger),
|
runSnapshotFn: (deviceIds, trigger) => _runScheduledSnapshot(deviceIds, trigger),
|
||||||
getAllDeviceIdsFn: () => Object.keys(_hostConfigs || {}),
|
getAllDeviceIdsFn: () => Object.keys(_hostConfigs || {}),
|
||||||
});
|
});
|
||||||
console.error("[RS_DEBUG] ✅ start() 已调用完成");
|
|
||||||
} else {
|
|
||||||
console.error("[RS_DEBUG] ⚠️ 已在运行中");
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[RS_DEBUG] ❌ 启动失败:", e?.message || String(e), e?.stack);
|
console.error("[Inspection] reportScheduler 自动启动失败:", e?.message || String(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -94,10 +94,12 @@ function _severityLabel(sev) { return _severityInfo(sev).label; }
|
|||||||
|
|
||||||
/** 健康分数 → 等级文本 */
|
/** 健康分数 → 等级文本 */
|
||||||
function _healthLevelText(score) {
|
function _healthLevelText(score) {
|
||||||
if (score >= 85) return "[健康]";
|
if (score >= 90) return "[优秀]";
|
||||||
if (score >= 70) return "[良好],存在少量隐患";
|
if (score >= 80) return "[健康]";
|
||||||
if (score >= 50) return "[一般],需重点处置风险";
|
if (score >= 70) return "[良好]";
|
||||||
return "[较差],存在严重故障,需优先处置";
|
if (score >= 60) return "[一般]";
|
||||||
|
if (score >= 40) return "[较差]";
|
||||||
|
return "[严重]";
|
||||||
}
|
}
|
||||||
|
|
||||||
function _severityColor(sev) {
|
function _severityColor(sev) {
|
||||||
@@ -110,42 +112,90 @@ function _severityColor(sev) {
|
|||||||
// ─── 健康评分算法 ────────────────────────────────────
|
// ─── 健康评分算法 ────────────────────────────────────
|
||||||
|
|
||||||
/** 单次巡检快照的健康评分(满分 100,最低 0) */
|
/** 单次巡检快照的健康评分(满分 100,最低 0) */
|
||||||
|
// ─── 健康评分算法(多维度加权) ─────────────────────
|
||||||
|
// 满分 100,四维度加权:
|
||||||
|
// Reachability 40% 设备可及性 —— 在线设备占比
|
||||||
|
// Coverage 25% 巡检覆盖率 —— 命令成功率(仅在线设备)
|
||||||
|
// Risk 25% 隐患评级 —— AI hidden_risks 加权,上限 20
|
||||||
|
// Efficiency 10% 执行效率 —— 超时/时长异常
|
||||||
|
//
|
||||||
|
// 设计思路:
|
||||||
|
// - 比例扣分而非线性叠加:设备越多,单台离线扣分越小
|
||||||
|
// - 离线设备不参与覆盖率计算(本来就连不上)
|
||||||
|
// - Risk 维度有上限,防止 AI 隐患把总分打崩
|
||||||
|
// - Efficiency 权重偏低,因耗时不直接反映健康度
|
||||||
|
|
||||||
function calcSnapshotHealthScore(snap, aiSummary) {
|
function calcSnapshotHealthScore(snap, aiSummary) {
|
||||||
let offlineCount = snap.devices.filter(d => !d.ok).length;
|
const devices = snap?.devices || [];
|
||||||
let critical = 0, warning = 0, info = 0, low = 0;
|
const total = devices.length;
|
||||||
const risks = []; // [{ severity, count }]
|
if (total === 0) {
|
||||||
|
return { score: 0, levelText: _healthLevelText(0), breakdown: "无设备数据" };
|
||||||
// AI 汇总里的 hidden_risks 按 severity 计数
|
|
||||||
if (aiSummary?.hidden_risks) {
|
|
||||||
for (const r of aiSummary.hidden_risks) {
|
|
||||||
const sev = r.severity || "info";
|
|
||||||
if (sev === "critical") critical++;
|
|
||||||
else if (sev === "warning") warning++;
|
|
||||||
else if (sev === "info") info++;
|
|
||||||
else low++;
|
|
||||||
risks.push({ severity: sev });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deduct = offlineCount * 8 + critical * 12 + warning * 6 + info * 3 + low * 1;
|
// ── 维度 1: 设备可及性(满分 40)─────────────────
|
||||||
const score = Math.max(0, 100 - deduct);
|
const online = devices.filter(d => d.ok).length;
|
||||||
return {
|
const offline = total - online;
|
||||||
score,
|
const reachabilityScore = Math.round(40 * (online / total));
|
||||||
levelText: _healthLevelText(score),
|
|
||||||
deduction: {
|
// ── 维度 2: 巡检覆盖率(满分 25)─────────────────
|
||||||
offline: { count: offlineCount, points: offlineCount * 8 },
|
let totalCmds = 0, okCmds = 0;
|
||||||
critical: { count: critical, points: critical * 12 },
|
for (const d of devices) {
|
||||||
warning: { count: warning, points: warning * 6 },
|
if (!d.ok) continue; // 离线跳过
|
||||||
info: { count: info, points: info * 3 },
|
const cmds = d.commands || [];
|
||||||
low: { count: low, points: low * 1 },
|
for (const c of cmds) {
|
||||||
},
|
totalCmds++;
|
||||||
};
|
// command not found / 未识别 是框架 fallback,不算失败
|
||||||
|
if (!c.ok && /command not found|未识别|not recognized/i.test(c.stderr || c.stdout || "")) continue;
|
||||||
|
if (c.ok) okCmds++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const coverageRatio = totalCmds === 0 ? 1 : okCmds / totalCmds;
|
||||||
|
const coverageScore = Math.round(25 * coverageRatio);
|
||||||
|
|
||||||
|
// ── 维度 3: 隐患评级(满分 25,上限扣 20)───────
|
||||||
|
const risks = aiSummary?.hidden_risks || [];
|
||||||
|
let riskPenalty = 0, riskCritical = 0, riskWarning = 0, riskInfo = 0;
|
||||||
|
for (const r of risks) {
|
||||||
|
if (!r || !r.severity) continue;
|
||||||
|
const sev = r.severity.toLowerCase();
|
||||||
|
if (sev === "critical") { riskPenalty += 5; riskCritical++; }
|
||||||
|
else if (sev === "warning" || sev === "high") { riskPenalty += 3; riskWarning++; }
|
||||||
|
else { riskPenalty += 1; riskInfo++; }
|
||||||
|
}
|
||||||
|
riskPenalty = Math.min(riskPenalty, 20); // 最多扣到只剩 5 分
|
||||||
|
const riskScore = 25 - riskPenalty;
|
||||||
|
|
||||||
|
// ── 维度 4: 执行效率(满分 10)───────────────────
|
||||||
|
let efficiencyScore = 10;
|
||||||
|
const durationMs = snap?.duration_ms || 0;
|
||||||
|
const avgMsPerDevice = total > 0 ? durationMs / total : 0;
|
||||||
|
if (avgMsPerDevice > 15000) {
|
||||||
|
const penalty = Math.min(6, Math.floor((avgMsPerDevice - 15000) / 10000));
|
||||||
|
efficiencyScore = 10 - penalty;
|
||||||
|
}
|
||||||
|
const timeoutDevices = devices.filter(d =>
|
||||||
|
d.ok && (d.commands || []).some(c => !c.ok && /ETIMEDOUT|timeout/i.test(c.stderr || c.stdout || ""))
|
||||||
|
).length;
|
||||||
|
if (timeoutDevices > 0) {
|
||||||
|
efficiencyScore = Math.max(4, efficiencyScore - Math.min(3, timeoutDevices));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 最终得分(四维度求和)───────────────────────
|
||||||
|
const score = Math.max(0, reachabilityScore + coverageScore + riskScore + efficiencyScore);
|
||||||
|
const breakdown = [
|
||||||
|
`设备可及性: ${reachabilityScore}/40(在线 ${online}/${total})`,
|
||||||
|
`巡检覆盖率: ${coverageScore}/25(命令成功率 ${(coverageRatio * 100).toFixed(1)}%)`,
|
||||||
|
`隐患评级: ${riskScore}/25(隐患 ${risks.length} 条: 严重 ${riskCritical}, 警告 ${riskWarning}, 提示 ${riskInfo})`,
|
||||||
|
`执行效率: ${efficiencyScore}/10(平均 ${(avgMsPerDevice / 1000).toFixed(1)}s/台${timeoutDevices > 0 ? `, ${timeoutDevices} 台超时` : ""})`,
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
return { score, levelText: _healthLevelText(score), breakdown };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 日报告健康评分(基于每个快照的分数,取算术平均 + max/min) */
|
/** 日报告健康评分(基于每个快照的分数,取算术平均 + max/min) */
|
||||||
function calcDailyHealthScore(snapScores) {
|
function calcDailyHealthScore(snapScores) {
|
||||||
if (!snapScores || snapScores.length === 0) {
|
if (!snapScores || snapScores.length === 0) {
|
||||||
return { score: 0, levelText: "[一般],无快照数据", max: 0, min: 0 };
|
return { score: 0, levelText: _healthLevelText(0), max: 0, min: 0 };
|
||||||
}
|
}
|
||||||
const sum = snapScores.reduce((a, b) => a + b, 0);
|
const sum = snapScores.reduce((a, b) => a + b, 0);
|
||||||
const score = Math.round(sum / snapScores.length);
|
const score = Math.round(sum / snapScores.length);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const dailyReportStore = require("./dailyReportStore.cjs");
|
|||||||
let _tickTimer = null;
|
let _tickTimer = null;
|
||||||
let _dailyTimer = null;
|
let _dailyTimer = null;
|
||||||
let _lastDailyTriggerKey = null; // 用于 HH:MM 日报告每日只触发一次
|
let _lastDailyTriggerKey = null; // 用于 HH:MM 日报告每日只触发一次
|
||||||
let _deviceLastRun = new Map(); // deviceId → timestamp(用于 per-device interval)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 reportScheduler
|
* 启动 reportScheduler
|
||||||
@@ -39,11 +38,19 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
|||||||
|
|
||||||
const tick = async () => {
|
const tick = async () => {
|
||||||
const cfg = reportScheduleStore.getSchedule(readFn);
|
const cfg = reportScheduleStore.getSchedule(readFn);
|
||||||
// 临时验证:忽略 cfg.enabled
|
if (!cfg.enabled) return;
|
||||||
// if (!cfg.enabled) return;
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const dueTargets = [];
|
const intervalMs = (cfg.defaultIntervalHours || 1) * 3600 * 1000;
|
||||||
|
const lastRun = cfg.lastRunAt || 0;
|
||||||
|
const sinceLast = now - lastRun;
|
||||||
|
|
||||||
|
// 还没到间隔就跳过
|
||||||
|
if (lastRun > 0 && sinceLast < intervalMs) {
|
||||||
|
const remainingMin = ((intervalMs - sinceLast) / 60000).toFixed(1);
|
||||||
|
console.log(`[reportScheduler] tick: 未到间隔,还需 ${remainingMin} 分钟`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// null/undefined = 全部;空数组 = 不巡检;非空数组 = 指定设备
|
// null/undefined = 全部;空数组 = 不巡检;非空数组 = 指定设备
|
||||||
let deviceIds = cfg.enabledDeviceIds;
|
let deviceIds = cfg.enabledDeviceIds;
|
||||||
@@ -51,7 +58,6 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
|||||||
// 全部设备 —— 通过回调拿(由调用方注入,避免依赖不存在的 deviceStore.listDevices)
|
// 全部设备 —— 通过回调拿(由调用方注入,避免依赖不存在的 deviceStore.listDevices)
|
||||||
if (typeof _getAllDeviceIds === "function") {
|
if (typeof _getAllDeviceIds === "function") {
|
||||||
deviceIds = _getAllDeviceIds();
|
deviceIds = _getAllDeviceIds();
|
||||||
console.error("[reportScheduler] tick: enabledDeviceIds=null, _getAllDeviceIds →", deviceIds?.length || 0, "台");
|
|
||||||
} else {
|
} else {
|
||||||
console.error("[reportScheduler] ❌ 无 getAllDeviceIds 回调,跳过");
|
console.error("[reportScheduler] ❌ 无 getAllDeviceIds 回调,跳过");
|
||||||
return;
|
return;
|
||||||
@@ -59,37 +65,35 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
|||||||
} else if (Array.isArray(deviceIds) && deviceIds.length === 0) {
|
} else if (Array.isArray(deviceIds) && deviceIds.length === 0) {
|
||||||
console.log("[reportScheduler] tick: enabledDeviceIds=[], 跳过(不巡检任何设备)");
|
console.log("[reportScheduler] tick: enabledDeviceIds=[], 跳过(不巡检任何设备)");
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
console.log("[reportScheduler] tick: enabledDeviceIds 非空数组 →", JSON.stringify(deviceIds));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const deviceId of deviceIds || []) {
|
// 过滤掉 isDeviceEnabled=false 的(disabled 状态)
|
||||||
if (!reportScheduleStore.isDeviceEnabled(readFn, deviceId)) continue;
|
const enabledDeviceIds = (deviceIds || []).filter(id =>
|
||||||
// 临时验证:强制 2 分钟间隔 + 首次立即触发
|
reportScheduleStore.isDeviceEnabled(readFn, id)
|
||||||
const intervalMs = 2 * 60 * 1000;
|
);
|
||||||
const lastRun = _deviceLastRun.get(deviceId) || 0;
|
|
||||||
if (now - lastRun >= intervalMs) {
|
if (enabledDeviceIds.length === 0) {
|
||||||
dueTargets.push(deviceId);
|
console.log("[reportScheduler] tick: 0 台设备启用,跳过");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[reportScheduler] tick: 总设备", deviceIds?.length || 0, "台, 到点", dueTargets.length, "台");
|
console.log(`[reportScheduler] tick: 到点,${enabledDeviceIds.length} 台设备,interval=${cfg.defaultIntervalHours}h,距上次 ${(sinceLast/60000).toFixed(1)} 分钟`);
|
||||||
if (dueTargets.length === 0) return;
|
|
||||||
console.log(`[reportScheduler] tick: ${dueTargets.length} 台设备到点,interval=${cfg.defaultIntervalHours}h`);
|
|
||||||
try {
|
try {
|
||||||
await runSnapshotFn(dueTargets, "scheduled");
|
await runSnapshotFn(enabledDeviceIds, "scheduled");
|
||||||
for (const id of dueTargets) _deviceLastRun.set(id, now);
|
// 持久化 lastRunAt —— 重启 Electron 也知道上次什么时候跑的
|
||||||
|
const merged = { ...cfg, lastRunAt: now };
|
||||||
|
reportScheduleStore.saveSchedule(writeFn, merged);
|
||||||
|
console.log("[reportScheduler] ✅ 快照完成,lastRunAt 已持久化");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[reportScheduler] runSnapshot 失败:", e?.message || String(e));
|
console.error("[reportScheduler] runSnapshot 失败:", e?.message || String(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 临时验证:启动后立刻执行一次 tick(不等间隔)
|
// 启动后等 60s tick 自动处理第一次(不立即触发,避免重启爆发)
|
||||||
console.log("[reportScheduler] 启动,立即执行首次 tick");
|
console.log("[reportScheduler] 已启动,间隔", (cfg.defaultIntervalHours || 1), "h,下次 tick 检查在 60s 后");
|
||||||
tick().catch(e => console.error("[reportScheduler] 首次 tick 异常:", e));
|
|
||||||
|
|
||||||
// 临时验证:每 5 秒 tick 一次
|
// 每 60 秒 tick 一次
|
||||||
_tickTimer = setInterval(() => { tick().catch(e => console.error("[reportScheduler] tick 异常:", e)); }, 5 * 1000);
|
_tickTimer = setInterval(() => { tick().catch(e => console.error("[reportScheduler] tick 异常:", e)); }, 60 * 1000);
|
||||||
_tickTimer.unref?.();
|
_tickTimer.unref?.();
|
||||||
|
|
||||||
// 每秒检查是否到 dailyReportTime(HH:MM 精确匹配,每日只触发一次)
|
// 每秒检查是否到 dailyReportTime(HH:MM 精确匹配,每日只触发一次)
|
||||||
@@ -117,8 +121,6 @@ function start({ readStorage, writeStorage, runSnapshotFn, onDailyReportReady, g
|
|||||||
};
|
};
|
||||||
_dailyTimer = setInterval(dailyCheck, 30 * 1000);
|
_dailyTimer = setInterval(dailyCheck, 30 * 1000);
|
||||||
_dailyTimer.unref?.();
|
_dailyTimer.unref?.();
|
||||||
|
|
||||||
console.log("[reportScheduler] 已启动");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
|
|||||||
@@ -304,13 +304,10 @@ async function afterPack(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.electronPlatformName === "win32") {
|
if (context.electronPlatformName === "win32") {
|
||||||
const projectDir = context.packager.appDir || context.packager.projectDir;
|
// [内网跳过] node-pty rebuild + ConPTY patch copy
|
||||||
rebuildPatchedNodePty({ projectDir, platform: "win32", arch: context.arch });
|
// 需要 MSVC cl.exe + 访问 www.electronjs.org 下载 headers
|
||||||
copyPatchedNodePtyToPackagedApp({
|
// 当前无 VS C++ 工具链,先跳过 —— release 时重新开启
|
||||||
projectDir,
|
console.log("[afterPack] ⚠️ Windows: 跳过 node-pty ConPTY patch rebuild + copy(内网 / 无 MSVC)");
|
||||||
resourcesDir: appResourcesDir(context),
|
|
||||||
});
|
|
||||||
console.log("[afterPack] Installed patched node-pty ConPTY runtime into packaged app");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,3 +362,4 @@ module.exports.adHocSignAppBundle = adHocSignAppBundle;
|
|||||||
module.exports.readAsarHeader = readAsarHeader;
|
module.exports.readAsarHeader = readAsarHeader;
|
||||||
module.exports.pruneAsarHeaderEntries = pruneAsarHeaderEntries;
|
module.exports.pruneAsarHeaderEntries = pruneAsarHeaderEntries;
|
||||||
module.exports.pruneCursorSdkPlatformPackages = pruneCursorSdkPlatformPackages;
|
module.exports.pruneCursorSdkPlatformPackages = pruneCursorSdkPlatformPackages;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user