503 lines
16 KiB
JavaScript
503 lines
16 KiB
JavaScript
|
|
"use strict";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 巡检模块统一入口
|
|||
|
|
*
|
|||
|
|
* 提供 IPC handler 注册函数,供主进程注册。
|
|||
|
|
* 渲染进程通过 IPC 调用巡检相关功能。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const deviceStore = require("./deviceStore.cjs");
|
|||
|
|
const alertRuleStore = require("./alertRuleStore.cjs");
|
|||
|
|
const inspectionResultStore = require("./inspectionResultStore.cjs");
|
|||
|
|
const alertRecordStore = require("./alertRecordStore.cjs");
|
|||
|
|
const adapterRegistry = require("./adapterRegistry.cjs");
|
|||
|
|
const InspectionScheduler = require("./inspectionScheduler.cjs");
|
|||
|
|
const { execSshCommand } = require("./inspectionExec.cjs");
|
|||
|
|
const { executeBoundedSshCommand } = require("../boundedSshExec.cjs");
|
|||
|
|
const {
|
|||
|
|
ALERT_LEVEL_LABELS,
|
|||
|
|
DEVICE_TIER_LABELS,
|
|||
|
|
INSPECTION_STATUS_LABELS,
|
|||
|
|
ALERT_RECORD_STATUS_LABELS,
|
|||
|
|
OPERATOR_LABELS,
|
|||
|
|
DEVICE_CATALOG_LABELS,
|
|||
|
|
METRIC_LABELS,
|
|||
|
|
INSPECTION_STATUS,
|
|||
|
|
} = require("./types.cjs");
|
|||
|
|
|
|||
|
|
let _scheduler = null;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取标签映射(前端展示用)
|
|||
|
|
*/
|
|||
|
|
function getLabels() {
|
|||
|
|
return {
|
|||
|
|
alert_levels: ALERT_LEVEL_LABELS,
|
|||
|
|
device_tiers: DEVICE_TIER_LABELS,
|
|||
|
|
inspection_status: INSPECTION_STATUS_LABELS,
|
|||
|
|
alert_record_status: ALERT_RECORD_STATUS_LABELS,
|
|||
|
|
operators: OPERATOR_LABELS,
|
|||
|
|
device_catalog: DEVICE_CATALOG_LABELS,
|
|||
|
|
metrics: METRIC_LABELS,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 注册巡检模块的 IPC handlers
|
|||
|
|
*
|
|||
|
|
* @param {object} ipcMain - Electron ipcMain
|
|||
|
|
* @param {object} options
|
|||
|
|
* @param {function} options.readStorage - 读取本地存储 (key) => string
|
|||
|
|
* @param {function} options.writeStorage - 写入本地存储 (key, value) => void
|
|||
|
|
*/
|
|||
|
|
function registerInspectionHandlers(ipcMain, options) {
|
|||
|
|
const { readStorage, writeStorage, sessions } = options;
|
|||
|
|
|
|||
|
|
const readFn = (key) => readStorage(key);
|
|||
|
|
const writeFn = (key, val) => writeStorage(key, val);
|
|||
|
|
|
|||
|
|
// 根据主机地址查找会话(用于复用终端 SSH 连接)
|
|||
|
|
function findSessionByHost(hostname) {
|
|||
|
|
const DEBUG = process.env.INSPECTION_DEBUG === "1";
|
|||
|
|
if (DEBUG) console.log("[Inspection] 查找会话,总会话数:", sessions?.size || 0);
|
|||
|
|
if (!sessions) {
|
|||
|
|
if (DEBUG) console.log("[Inspection] sessions 未初始化");
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
for (const session of sessions.values()) {
|
|||
|
|
if (!session) continue;
|
|||
|
|
// SSH 会话才有 conn
|
|||
|
|
if (!session.conn) continue;
|
|||
|
|
// 匹配主机地址
|
|||
|
|
const sessionHost = session.hostname || session.host || "";
|
|||
|
|
if (DEBUG) {
|
|||
|
|
console.log("[Inspection] SSH会话:", sessionHost, "| 目标:", hostname);
|
|||
|
|
}
|
|||
|
|
if (sessionHost === hostname) {
|
|||
|
|
if (DEBUG) console.log("[Inspection] 匹配到会话:", sessionHost);
|
|||
|
|
return session;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------- 标签映射 ----------
|
|||
|
|
ipcMain.handle("inspection:getLabels", () => {
|
|||
|
|
return getLabels();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 系统配置 ----------
|
|||
|
|
ipcMain.handle("inspection:getSysConfig", () => {
|
|||
|
|
return deviceStore.getSysConfig(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:updateSysConfig", (_e, updates) => {
|
|||
|
|
return deviceStore.updateSysConfig(readFn, writeFn, updates);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 设备类型 ----------
|
|||
|
|
ipcMain.handle("inspection:getCatalog", () => {
|
|||
|
|
return deviceStore.getCatalog();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getTiers", () => {
|
|||
|
|
return deviceStore.getTiers();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getAdapters", () => {
|
|||
|
|
return adapterRegistry.getRegisteredAdapters();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 设备扩展配置 ----------
|
|||
|
|
ipcMain.handle("inspection:getAllExtensions", () => {
|
|||
|
|
return deviceStore.getAllExtensions(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getExtension", (_e, hostId) => {
|
|||
|
|
return deviceStore.getExtension(readFn, hostId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:upsertExtension", (_e, hostId, data) => {
|
|||
|
|
return deviceStore.upsertExtension(readFn, writeFn, hostId, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:deleteExtension", (_e, hostId) => {
|
|||
|
|
return deviceStore.deleteExtension(readFn, writeFn, hostId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getEffectiveInterval", (_e, hostId) => {
|
|||
|
|
return deviceStore.getEffectiveInterval(readFn, hostId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getEnabledDevices", () => {
|
|||
|
|
return deviceStore.getEnabledDevices(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 告警规则 ----------
|
|||
|
|
ipcMain.handle("inspection:listRules", () => {
|
|||
|
|
return alertRuleStore.listRules(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getRule", (_e, ruleId) => {
|
|||
|
|
return alertRuleStore.getRule(readFn, ruleId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:createRule", (_e, data) => {
|
|||
|
|
return alertRuleStore.createRule(readFn, writeFn, data);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:updateRule", (_e, ruleId, updates) => {
|
|||
|
|
return alertRuleStore.updateRule(readFn, writeFn, ruleId, updates);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:deleteRule", (_e, ruleId) => {
|
|||
|
|
return alertRuleStore.deleteRule(readFn, writeFn, ruleId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:toggleRule", (_e, ruleId, enabled) => {
|
|||
|
|
return alertRuleStore.toggleRule(readFn, writeFn, ruleId, enabled);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 巡检结果 ----------
|
|||
|
|
ipcMain.handle("inspection:getLatestAll", () => {
|
|||
|
|
return inspectionResultStore.getLatestAll(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getLatestByDevice", (_e, hostId, limit) => {
|
|||
|
|
return inspectionResultStore.getLatestByDevice(readFn, hostId, limit);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getRecentResults", (_e, limit) => {
|
|||
|
|
return inspectionResultStore.getRecent(readFn, limit);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 单次巡检执行(前端传入完整主机配置) ----------
|
|||
|
|
ipcMain.handle("inspection:runInspection", async (_e, payload) => {
|
|||
|
|
const { hostId, hostConfig, catalogId } = payload || {};
|
|||
|
|
|
|||
|
|
if (!hostId || !hostConfig) {
|
|||
|
|
return { success: false, error: "缺少主机配置" };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const DEBUG = process.env.INSPECTION_DEBUG === "1";
|
|||
|
|
if (DEBUG) {
|
|||
|
|
console.log("[Inspection] 巡检配置:", JSON.stringify({
|
|||
|
|
host: hostConfig.hostname || hostConfig.address,
|
|||
|
|
port: hostConfig.port || 22,
|
|||
|
|
username: hostConfig.username,
|
|||
|
|
hasPassword: !!hostConfig.password,
|
|||
|
|
hasPrivateKey: !!hostConfig.privateKeyContent,
|
|||
|
|
hasKeyId: !!hostConfig.identityFileId,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const sshConfig = {
|
|||
|
|
host: hostConfig.hostname || hostConfig.address,
|
|||
|
|
port: hostConfig.port || 22,
|
|||
|
|
username: hostConfig.username,
|
|||
|
|
password: hostConfig.password,
|
|||
|
|
privateKey: hostConfig.privateKeyContent,
|
|||
|
|
passphrase: hostConfig.passphrase,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!sshConfig.host || !sshConfig.username) {
|
|||
|
|
return { success: false, error: "主机地址或用户名缺失" };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const startTime = Date.now();
|
|||
|
|
const result = {
|
|||
|
|
id: `ins_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|||
|
|
host_id: hostId,
|
|||
|
|
catalog_id: catalogId || "linux_server",
|
|||
|
|
start_time: startTime,
|
|||
|
|
end_time: 0,
|
|||
|
|
status: INSPECTION_STATUS.SUCCESS,
|
|||
|
|
metrics: {},
|
|||
|
|
raw: {},
|
|||
|
|
error: null,
|
|||
|
|
alerts: [],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 构造 execFn — 优先复用终端会话的 SSH 连接
|
|||
|
|
const execFn = async (command, options = {}) => {
|
|||
|
|
const timeoutMs = options.timeoutMs || 60000;
|
|||
|
|
const DEBUG = process.env.INSPECTION_DEBUG === "1";
|
|||
|
|
|
|||
|
|
// 1. 先尝试复用终端会话的连接
|
|||
|
|
const session = findSessionByHost(sshConfig.host);
|
|||
|
|
if (DEBUG) {
|
|||
|
|
console.log("[Inspection] 查找会话:", sshConfig.host,
|
|||
|
|
"| 找到会话:", session ? "是" : "否",
|
|||
|
|
"| 有连接:", session?.conn ? "是" : "否");
|
|||
|
|
}
|
|||
|
|
if (session && session.conn) {
|
|||
|
|
try {
|
|||
|
|
const result = await executeBoundedSshCommand(session.conn, command, {
|
|||
|
|
openingTimeoutMs: timeoutMs,
|
|||
|
|
runTimeoutMs: timeoutMs,
|
|||
|
|
maxOutputBytes: 100 * 1024,
|
|||
|
|
});
|
|||
|
|
if (DEBUG) console.log("[Inspection] 复用会话成功, stdout:", result.stdout?.slice(0, 100));
|
|||
|
|
return result.stdout + (result.stderr ? "\n" + result.stderr : "");
|
|||
|
|
} catch (e) {
|
|||
|
|
// 终端连接失败,回退到新建连接
|
|||
|
|
if (DEBUG) console.log("[Inspection] 复用会话失败,回退:", e.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 回退:新建 SSH 连接
|
|||
|
|
return await execSshCommand(sshConfig, command, {
|
|||
|
|
connectTimeoutMs: 10000,
|
|||
|
|
execTimeoutMs: timeoutMs,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 构造批量执行函数(一次连接执行多条命令)
|
|||
|
|
const execCommandsFn = async (commands, options = {}) => {
|
|||
|
|
const { execSshCommands } = require("./inspectionExec.cjs");
|
|||
|
|
return await execSshCommands(sshConfig, commands, options);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取设备类型对应的采集器类名
|
|||
|
|
const catalogs = deviceStore.getCatalog();
|
|||
|
|
const catalog = catalogs.find((c) => c.id === catalogId);
|
|||
|
|
const adapterClassName = catalog?.adapter_class || "LinuxServerAdapter";
|
|||
|
|
|
|||
|
|
// 创建采集器实例(注入 execFn 和 execCommandsFn)
|
|||
|
|
const adapter = adapterRegistry.createAdapter(adapterClassName, sshConfig, {
|
|||
|
|
execFn,
|
|||
|
|
execCommands: execCommandsFn,
|
|||
|
|
timeoutMs: 20000,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!adapter) {
|
|||
|
|
throw new Error("采集器加载失败: " + adapterClassName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const collectResult = await adapter.collect();
|
|||
|
|
|
|||
|
|
result.metrics = collectResult.metrics || {};
|
|||
|
|
result.raw = collectResult.raw || {};
|
|||
|
|
|
|||
|
|
if (!collectResult.online) {
|
|||
|
|
result.status = collectResult.status || INSPECTION_STATUS.FAILED;
|
|||
|
|
result.error = collectResult.error || "设备离线或连接失败";
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
result.status = INSPECTION_STATUS.FAILED;
|
|||
|
|
result.error = err.message;
|
|||
|
|
console.error("[Inspection] runInspection error:", err.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.end_time = Date.now();
|
|||
|
|
const duration = result.end_time - result.start_time;
|
|||
|
|
|
|||
|
|
// 构造设备信息(用于告警记录)
|
|||
|
|
const deviceInfo = {
|
|||
|
|
device_id: hostId,
|
|||
|
|
device_name: hostConfig.label || hostConfig.hostname || hostId,
|
|||
|
|
catalog_id: catalogId || "linux_server",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存巡检结果
|
|||
|
|
try {
|
|||
|
|
inspectionResultStore.addResult(readFn, writeFn, {
|
|||
|
|
...result,
|
|||
|
|
device_id: hostId,
|
|||
|
|
online: result.status === INSPECTION_STATUS.SUCCESS,
|
|||
|
|
duration_ms: duration,
|
|||
|
|
error_message: result.error,
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn("[Inspection] 保存巡检结果失败:", e.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 告警判断(成功/失败都要判断,特别是 online 指标)
|
|||
|
|
const triggeredAlerts = [];
|
|||
|
|
try {
|
|||
|
|
const deviceExt = deviceStore.getExtension(readFn, hostId);
|
|||
|
|
const rules = alertRuleStore.getRulesForDevice(readFn, deviceExt || { host_id: hostId, catalog_id: catalogId });
|
|||
|
|
const isOnline = result.status === INSPECTION_STATUS.SUCCESS;
|
|||
|
|
|
|||
|
|
for (const rule of rules) {
|
|||
|
|
// online 指标 — 成功/失败都要判断(设备离线告警)
|
|||
|
|
if (rule.metric === "online") {
|
|||
|
|
const shouldAlert = rule.threshold === "false" ? !isOnline : isOnline;
|
|||
|
|
console.log(`[Inspection] 离线规则: name=${rule.name} enabled=${rule.enabled} isOnline=${isOnline} shouldAlert=${shouldAlert}`);
|
|||
|
|
const alertResult = alertRecordStore.processAlert(
|
|||
|
|
readFn,
|
|||
|
|
writeFn,
|
|||
|
|
deviceInfo,
|
|||
|
|
rule,
|
|||
|
|
isOnline ? "在线" : "离线",
|
|||
|
|
shouldAlert
|
|||
|
|
);
|
|||
|
|
if (alertResult) {
|
|||
|
|
console.log(`[Inspection] 告警触发:`, JSON.stringify(alertResult));
|
|||
|
|
triggeredAlerts.push(alertResult);
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 普通指标 — 只在成功时判断(失败时没有指标数据)
|
|||
|
|
if (isOnline) {
|
|||
|
|
const metricValue = result.metrics[rule.metric];
|
|||
|
|
if (metricValue === undefined || metricValue === null) continue;
|
|||
|
|
|
|||
|
|
const isTriggered = alertRuleStore.evaluateRule(metricValue, rule.operator, rule.threshold);
|
|||
|
|
const alertResult = alertRecordStore.processAlert(
|
|||
|
|
readFn,
|
|||
|
|
writeFn,
|
|||
|
|
deviceInfo,
|
|||
|
|
rule,
|
|||
|
|
metricValue,
|
|||
|
|
isTriggered
|
|||
|
|
);
|
|||
|
|
if (alertResult) {
|
|||
|
|
triggeredAlerts.push(alertResult);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn("[Inspection] 告警判断失败:", e.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: result.status === INSPECTION_STATUS.SUCCESS,
|
|||
|
|
status: result.status,
|
|||
|
|
metrics: result.metrics,
|
|||
|
|
error: result.error,
|
|||
|
|
duration,
|
|||
|
|
alerts: triggeredAlerts,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ---------- 告警记录 ----------
|
|||
|
|
ipcMain.handle("inspection:listAlerts", (_e, options) => {
|
|||
|
|
return alertRecordStore.listAlerts(readFn, options || {});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getActiveAlerts", () => {
|
|||
|
|
return alertRecordStore.getActiveAlerts(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:getAlertStats", () => {
|
|||
|
|
return alertRecordStore.getAlertStats(readFn);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:acknowledgeAlert", (_e, alertId, operator) => {
|
|||
|
|
return alertRecordStore.acknowledgeAlert(readFn, writeFn, alertId, operator);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:resolveAlert", (_e, alertId, reason, operator) => {
|
|||
|
|
return alertRecordStore.resolveAlert(readFn, writeFn, alertId, reason, operator);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:closeAlert", (_e, alertId, note) => {
|
|||
|
|
return alertRecordStore.closeAlert(readFn, writeFn, alertId, note);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:updateAlertNote", (_e, alertId, note) => {
|
|||
|
|
return alertRecordStore.updateNote(readFn, writeFn, alertId, note);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 存储前端传入的设备 SSH 配置(hostId → sshConfig)
|
|||
|
|
let _hostConfigs = null;
|
|||
|
|
|
|||
|
|
// ---------- 调度器控制 ----------
|
|||
|
|
ipcMain.handle("inspection:scheduler:start", (_e, options = {}) => {
|
|||
|
|
// 前端可传入 { hostConfigs: { [hostId]: sshConfig } } 用于调度器执行 SSH 命令
|
|||
|
|
if (options.hostConfigs) {
|
|||
|
|
_hostConfigs = options.hostConfigs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从 hostConfigs 提取 hostInfo(hostname/label)用于告警显示
|
|||
|
|
const hostInfo = {};
|
|||
|
|
if (_hostConfigs) {
|
|||
|
|
for (const [hostId, config] of Object.entries(_hostConfigs)) {
|
|||
|
|
hostInfo[hostId] = {
|
|||
|
|
hostname: config.host || config.hostname || "",
|
|||
|
|
label: config.label || "",
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_scheduler) {
|
|||
|
|
_scheduler = new InspectionScheduler({
|
|||
|
|
readFn,
|
|||
|
|
writeFn,
|
|||
|
|
execFn: async (hostId, command) => {
|
|||
|
|
// 优先用前端传入的 hostConfigs
|
|||
|
|
if (_hostConfigs && _hostConfigs[hostId]) {
|
|||
|
|
const config = _hostConfigs[hostId];
|
|||
|
|
const { execSshCommand: execCmd } = require("./inspectionExec.cjs");
|
|||
|
|
return await execCmd(config, command, {
|
|||
|
|
connectTimeoutMs: 10000,
|
|||
|
|
execTimeoutMs: 30000,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// 兜底:尝试复用已有终端会话
|
|||
|
|
const session = findSessionByHost(hostId);
|
|||
|
|
if (session?.conn) {
|
|||
|
|
const { executeBoundedSshCommand } = require("../boundedSshExec.cjs");
|
|||
|
|
return await executeBoundedSshCommand(session.conn, command, {
|
|||
|
|
timeoutMs: 30000,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
throw new Error(`无法执行巡检:未找到主机 ${hostId} 的 SSH 配置`);
|
|||
|
|
},
|
|||
|
|
pollIntervalMs: 30 * 1000,
|
|||
|
|
concurrency: 5,
|
|||
|
|
timeoutMs: 30 * 1000,
|
|||
|
|
hostInfo,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
_scheduler.start();
|
|||
|
|
return _scheduler.getStatus();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:scheduler:stop", () => {
|
|||
|
|
if (_scheduler) {
|
|||
|
|
_scheduler.stop();
|
|||
|
|
return _scheduler.getStatus();
|
|||
|
|
}
|
|||
|
|
return { running: false };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:scheduler:status", () => {
|
|||
|
|
return _scheduler ? _scheduler.getStatus() : { running: false };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:scheduler:scanNow", (_e, hostId) => {
|
|||
|
|
if (!_scheduler) return { error: "调度器未启动" };
|
|||
|
|
return _scheduler.scanDeviceNow(hostId);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ipcMain.handle("inspection:scheduler:scanAllNow", () => {
|
|||
|
|
if (!_scheduler) return { error: "调度器未启动" };
|
|||
|
|
return _scheduler.scanAllNow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log("[Inspection] IPC handlers 已注册");
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
getScheduler: () => _scheduler,
|
|||
|
|
createScheduler: (opts) => {
|
|||
|
|
_scheduler = new InspectionScheduler({
|
|||
|
|
readFn,
|
|||
|
|
writeFn,
|
|||
|
|
execFn: execCommand,
|
|||
|
|
...opts,
|
|||
|
|
});
|
|||
|
|
return _scheduler;
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
registerInspectionHandlers,
|
|||
|
|
getLabels,
|
|||
|
|
};
|