feat: 巡检增强 / 拓扑大屏 / 远程 HTTP / portable 绿色打包
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
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
巡检模块:巡检调度器改为轮询驱动,新增每日报告存储、告警规则独立 Store、设备 Store 解耦,_executeSnapshot 补齐 DEBUG 声明,巡检中心 与告警中心页增强。 拓扑大屏:MonitoringTopologyView 扩展分组/布局控制, useMonitoringData 重构数据流。 远程 HTTP:新增 electron/remoteHttpServer.cjs,preload/api.cjs 暴露 IPC,registerBridges 注册 netcatty:http:open 桥。 portable 绿色打包: - electron-builder.config.cjs 恢复原始白名单(61MB app.asar), 收集器自动追踪运行时依赖; - portableData.cjs 重写为 .portable 标记检测; - scripts/migratePortableLocalStorage.cjs 离线脚本把 Local Storage 的 http://localhost:5173 origin 批量迁移为 app://netcatty; - scripts/scanWorkspaceDeps.cjs 扫描 workspace 运行时依赖。
This commit is contained in:
@@ -130,16 +130,137 @@ export interface SchedulerStatus {
|
||||
|
||||
const api = (window as any).netcatty?.inspection;
|
||||
|
||||
// 浏览器远程访问环境:通过 HTTP API fallback
|
||||
const IS_REMOTE_BROWSER = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
|
||||
const REMOTE_TOKEN = (window as any).__REMOTE_TOKEN__ || "";
|
||||
|
||||
const REMOTE_API_MAP: Record<string, { method: string; path: string; arrayField?: string }> = {
|
||||
getLatestAll: { method: "GET", path: "/api/inspection/results", arrayField: "results" },
|
||||
getLatestByDevice: { method: "GET", path: "/api/inspection/results", arrayField: "results" },
|
||||
getRecentResults: { method: "GET", path: "/api/inspection/results/recent", arrayField: "results" },
|
||||
getActiveAlerts: { method: "GET", path: "/api/alerts", arrayField: "alerts" },
|
||||
listAlerts: { method: "GET", path: "/api/alerts", arrayField: "alerts" },
|
||||
getAlertStats: { method: "GET", path: "/api/alert-stats" },
|
||||
listRules: { method: "GET", path: "/api/alert-rules", arrayField: "rules" },
|
||||
getRule: { method: "GET", path: "/api/alert-rules", arrayField: "rules" },
|
||||
getAllExtensions: { method: "GET", path: "/api/devices", arrayField: "devices" },
|
||||
getEnabledDevices: { method: "GET", path: "/api/devices/enabled", arrayField: "devices" },
|
||||
schedulerStatus: { method: "GET", path: "/api/inspection/scheduler-status" },
|
||||
getCatalog: { method: "GET", path: "/api/inspection/catalog", arrayField: "catalog" },
|
||||
getSysConfig: { method: "GET", path: "/api/inspection/sys-config" },
|
||||
getLabels: { method: "GET", path: "/api/inspection/labels" },
|
||||
listReportSnapshots: { method: "GET", path: "/api/report/snapshots", arrayField: "snapshots" },
|
||||
isReportSnapshotRunning: { method: "GET", path: "/api/report/snapshot-status" },
|
||||
getReportSchedule: { method: "GET", path: "/api/report/schedule" },
|
||||
listDailyReports: { method: "GET", path: "/api/daily-reports", arrayField: "reports" },
|
||||
batchSetInspectionEnabled: { method: "POST", path: "/api/devices/batch-enable" },
|
||||
runReportAiSummary: { method: "POST", path: "/api/run-report-ai-summary" },
|
||||
generateDailyReport: { method: "POST", path: "/api/generate-daily-report" },
|
||||
};
|
||||
|
||||
// 某些 API 返回数组 —— 在 REMOTE_API_MAP 里用 arrayField 标记
|
||||
// 找不到映射的 API 默认返回 null,让调用方自己兜底
|
||||
const ARRAY_METHODS = new Set([
|
||||
"listReportSnapshots", "listDailyReports", "listRules", "getLatestAll",
|
||||
"getLatestByDevice", "getRecentResults", "getActiveAlerts", "listAlerts",
|
||||
"getAllExtensions", "getEnabledDevices", "getCatalog",
|
||||
]);
|
||||
|
||||
async function remoteFetch(name: string, ...args: any[]): Promise<any> {
|
||||
const mapping = REMOTE_API_MAP[name];
|
||||
if (!mapping) {
|
||||
// 没有映射的方法:读场景返回空数组/空对象兜底,避免 .find 崩溃
|
||||
console.warn(`[Inspection] remote fallback: no mapping for ${name}, returning safe default`);
|
||||
if (ARRAY_METHODS.has(name)) return [];
|
||||
if (name === "getSchedule" || name === "getReportSchedule") return {};
|
||||
if (name === "getSysConfig") return {};
|
||||
if (name === "getLabels") return { alert_levels: {}, device_tiers: {}, inspection_status: {}, alert_record_status: {}, operators: {}, device_catalog: {}, metrics: {} };
|
||||
if (name === "schedulerStatus" || name === "isReportSnapshotRunning") return { running: false };
|
||||
return null;
|
||||
}
|
||||
let url = `${mapping.path}?token=${encodeURIComponent(REMOTE_TOKEN)}`;
|
||||
if (name === "getLatestByDevice" && args[0]) {
|
||||
url += `&device_id=${encodeURIComponent(args[0])}`;
|
||||
}
|
||||
if ((name === "listAlerts" || name === "listReportSnapshots") && args[0] !== undefined) {
|
||||
const opts = typeof args[0] === "object" ? args[0] : { limit: args[0] };
|
||||
if (opts.status) url += `&status=${opts.status}`;
|
||||
if (opts.level) url += `&level=${opts.level}`;
|
||||
if (opts.device_id) url += `&device_id=${opts.device_id}`;
|
||||
if (opts.limit) url += `&limit=${opts.limit}`;
|
||||
} else if (args[0] !== undefined && name !== "getRule") {
|
||||
url += `&limit=${encodeURIComponent(args[0])}`;
|
||||
}
|
||||
try {
|
||||
const fetchOpts: RequestInit = {};
|
||||
if (mapping.method === "POST") {
|
||||
fetchOpts.method = "POST";
|
||||
fetchOpts.headers = { "Content-Type": "application/json" };
|
||||
if (name === "batchSetInspectionEnabled") {
|
||||
fetchOpts.body = JSON.stringify({ enabled: !!args[0] });
|
||||
} else if (name === "runReportAiSummary") {
|
||||
// invoke("runReportAiSummary", snapshotId, providerId)
|
||||
fetchOpts.body = JSON.stringify({ snapshotId: args[0], providerId: args[1] });
|
||||
} else if (name === "generateDailyReport") {
|
||||
// invoke("generateDailyReport", options)
|
||||
fetchOpts.body = JSON.stringify(args[0] || {});
|
||||
} else {
|
||||
fetchOpts.body = JSON.stringify(args[0] !== undefined ? args[0] : {});
|
||||
}
|
||||
}
|
||||
const res = await fetch(url, fetchOpts);
|
||||
const data = await res.json();
|
||||
// 适配不同返回结构
|
||||
if (data && typeof data === "object") {
|
||||
if (Array.isArray(data)) return data;
|
||||
// 优先用 arrayField 约定的字段
|
||||
if (mapping.arrayField && Array.isArray(data[mapping.arrayField])) {
|
||||
return data[mapping.arrayField];
|
||||
}
|
||||
if (data.alerts !== undefined) return data.alerts;
|
||||
if (data.devices !== undefined) {
|
||||
if (name === "getAllExtensions") {
|
||||
// getAllExtensions 返回 Record<string, DeviceExtension>
|
||||
const map: Record<string, any> = {};
|
||||
for (const d of data.devices) {
|
||||
map[d.host_id] = d;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
return data.devices;
|
||||
}
|
||||
if (data.rules !== undefined) return data.rules;
|
||||
if (data.results !== undefined) return data.results;
|
||||
if (data.snapshots !== undefined) return data.snapshots;
|
||||
if (data.reports !== undefined) return data.reports;
|
||||
if (data.schedule !== undefined) return data.schedule;
|
||||
if (data.ok === true && data.data !== undefined) return data.data;
|
||||
if ("running" in data) return data; // schedulerStatus / snapshot-status
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.warn(`[Inspection] remote fetch ${name} failed:`, e);
|
||||
// fetch 失败也返回安全默认值,不要让 .find 崩
|
||||
if (ARRAY_METHODS.has(name)) return [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isAvailable(): boolean {
|
||||
return !!api;
|
||||
return !!api || IS_REMOTE_BROWSER;
|
||||
}
|
||||
|
||||
function invoke(name: string, ...args: any[]): Promise<any> {
|
||||
if (!api || typeof api[name] !== "function") {
|
||||
console.warn(`[Inspection] api.${name} 不可用`);
|
||||
return Promise.resolve(null);
|
||||
if (api && typeof api[name] === "function") {
|
||||
return api[name](...args);
|
||||
}
|
||||
return api[name](...args);
|
||||
// Electron bridge 不可用 → 浏览器远程访问环境走 HTTP fallback
|
||||
if (IS_REMOTE_BROWSER) {
|
||||
return remoteFetch(name, ...args);
|
||||
}
|
||||
console.warn(`[Inspection] api.${name} 不可用`);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// ---------- 标签 ----------
|
||||
@@ -167,6 +288,8 @@ export const inspectionDeviceApi = {
|
||||
get: (hostId: string): Promise<DeviceExtension | null> => invoke("getExtension", hostId),
|
||||
upsert: (hostId: string, data: Partial<DeviceExtension>): Promise<DeviceExtension> =>
|
||||
invoke("upsertExtension", hostId, data),
|
||||
batchSetEnabled: (enabled: boolean): Promise<{ ok: boolean; count: number; enabled: boolean; error?: string }> =>
|
||||
invoke("batchSetInspectionEnabled", enabled),
|
||||
remove: (hostId: string): Promise<boolean> => invoke("deleteExtension", hostId),
|
||||
getEffectiveInterval: (hostId: string): Promise<number> =>
|
||||
invoke("getEffectiveInterval", hostId),
|
||||
@@ -229,6 +352,8 @@ export const inspectionSchedulerApi = {
|
||||
status: (): Promise<SchedulerStatus> => invoke("schedulerStatus"),
|
||||
scanNow: (hostId: string): Promise<any> => invoke("scanNow", hostId),
|
||||
scanAllNow: (): Promise<any> => invoke("scanAllNow"),
|
||||
getAutoEnabled: (): Promise<boolean> => invoke("getAutoEnabled"),
|
||||
setAutoEnabled: (enabled: boolean): Promise<{ enabled: boolean }> => invoke("setAutoEnabled", enabled),
|
||||
};
|
||||
|
||||
// ---------- 单次巡检执行 ----------
|
||||
|
||||
Reference in New Issue
Block a user