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:
@@ -90,6 +90,9 @@ interface UseMonitoringDataResult {
|
||||
// 操作
|
||||
refresh: () => Promise<void>;
|
||||
loading: boolean;
|
||||
|
||||
// 浏览器远程访问环境下实际使用的 hosts(Electron 环境 = props.hosts)
|
||||
effectiveHosts: Host[];
|
||||
}
|
||||
|
||||
// ===== localStorage 持久化(降级用,API 不可用时显示缓存) =====
|
||||
@@ -321,6 +324,61 @@ export function useMonitoringData(hosts: Host[], keys: any[] = []): UseMonitorin
|
||||
hostsRef.current = hosts;
|
||||
keysRef.current = keys;
|
||||
|
||||
// 浏览器远程访问环境:从 /api/devices 构建虚拟 hosts 列表(Electron vault 不可用)
|
||||
const [remoteHosts, setRemoteHosts] = useState<Host[]>([]);
|
||||
const IS_REMOTE = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
|
||||
const REMOTE_TOKEN = (window as any).__REMOTE_TOKEN__ || "";
|
||||
|
||||
// Electron 本地:同步 vault hosts 给主进程(供远程浏览器 API 读)
|
||||
useEffect(() => {
|
||||
if (IS_REMOTE) return; // 远程浏览器不触发
|
||||
const nc = (window as any).netcatty;
|
||||
if (!nc?.inspection) return;
|
||||
if (hosts.length === 0) return;
|
||||
console.log('[useMonitoringData] syncing vault hosts to main process:', hosts.length);
|
||||
nc.inspection.saveVaultHosts(hosts).then((r: any) => {
|
||||
console.log('[useMonitoringData] saveVaultHosts result:', r);
|
||||
}).catch((e: any) => {
|
||||
console.warn('[useMonitoringData] saveVaultHosts error:', e);
|
||||
});
|
||||
}, [hosts, IS_REMOTE]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!IS_REMOTE) return;
|
||||
console.log('[monitoring] REMOTE ENV - props.hosts.length=', hosts.length, 'remoteHosts.length=', remoteHosts.length);
|
||||
if (hosts.length > 0) {
|
||||
console.warn('[monitoring] REMOTE ENV 但 props.hosts 有值!跳过 remote hosts 加载,hosts[0]=', hosts[0]);
|
||||
return;
|
||||
}
|
||||
const loadRemoteHosts = async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/devices?token=${encodeURIComponent(REMOTE_TOKEN)}`);
|
||||
const data = await res.json();
|
||||
const list: Host[] = (data.devices || []).map((d: any) => ({
|
||||
id: d.host_id,
|
||||
label: d.label || d.name || d.hostname || d.host_id,
|
||||
hostname: d.hostname || d.name || "",
|
||||
port: 22,
|
||||
username: "",
|
||||
distro: d.distro || d.catalog_type || d.catalog_id || "",
|
||||
type: d.type || "",
|
||||
catalogId: d.catalog_id || "",
|
||||
tags: [],
|
||||
os: "linux" as const,
|
||||
group: "",
|
||||
}));
|
||||
setRemoteHosts(list);
|
||||
hostsRef.current = list;
|
||||
} catch (e) {
|
||||
console.warn("[monitoring] remote hosts load failed:", e);
|
||||
}
|
||||
};
|
||||
void loadRemoteHosts();
|
||||
}, [IS_REMOTE, REMOTE_TOKEN, hosts.length]);
|
||||
|
||||
// 实际使用的 hosts:Electron 用 props,浏览器用远程加载的
|
||||
const effectiveHosts = IS_REMOTE && hosts.length === 0 ? remoteHosts : hosts;
|
||||
|
||||
const refresh = useCallback(async (silent = false) => {
|
||||
if (loadingRef.current) {
|
||||
console.log('[monitoring] refresh() 正在执行中,跳过');
|
||||
@@ -505,33 +563,33 @@ export function useMonitoringData(hosts: Host[], keys: any[] = []): UseMonitorin
|
||||
// 自动启动调度器
|
||||
if (enabledDevices.length === 0) {
|
||||
console.log('[monitoring] 没有启用巡检的设备');
|
||||
} else if (schedulerStatus.running) {
|
||||
console.log('[monitoring] 调度器已在运行中');
|
||||
} else {
|
||||
console.log('[monitoring] 条件满足,准备启动调度器');
|
||||
console.log('[monitoring] 条件满足,准备启动调度器');
|
||||
try {
|
||||
const hostConfigs: Record<string, Record<string, unknown>> = {};
|
||||
for (const dev of enabledDevices) {
|
||||
const hostname = dev.hostname;
|
||||
if (!hostname) {
|
||||
console.log('[monitoring] 设备', dev.host_id, '没有 hostname,跳过');
|
||||
continue;
|
||||
}
|
||||
const host = hostsRef.current.find((h) => h.hostname === hostname);
|
||||
const allExt = allExtensions || {};
|
||||
for (const [hostId, ext] of Object.entries(allExt)) {
|
||||
if (!ext || !ext.inspection_enabled) continue;
|
||||
const host = hostsRef.current.find((h) => h.id === hostId);
|
||||
if (!host) {
|
||||
console.log('[monitoring] vault 中未找到 hostname:', hostname, ',跳过');
|
||||
console.log('[monitoring] vault 中未找到 host_id:', hostId, ',跳过');
|
||||
continue;
|
||||
}
|
||||
if (!host.username) {
|
||||
console.log('[monitoring] 设备', hostname, '缺少 username,跳过');
|
||||
console.log('[monitoring] 设备', host.label || host.hostname || hostId, '缺少 username,跳过');
|
||||
continue;
|
||||
}
|
||||
hostConfigs[dev.host_id] = buildSshConfig(host, keysRef.current);
|
||||
hostConfigs[hostId] = buildSshConfig(host, keysRef.current);
|
||||
}
|
||||
// 诊断:打印 hostConfigs 样本看 label 有没有正确传递
|
||||
const sample = Object.entries(hostConfigs).slice(0, 3).map(([id, c]) => ({ id, label: c.label, host: c.host, username: c.username }));
|
||||
console.log('[monitoring] hostConfigs 样本:', JSON.stringify(sample));
|
||||
if (Object.keys(hostConfigs).length === 0) {
|
||||
console.log('[monitoring] 没有可巡检的设备(hostname 未写入或配置不完整)');
|
||||
console.log('[monitoring] 没有可巡检的设备(vault 中匹配不到或凭据不完整)');
|
||||
} else {
|
||||
console.log('[monitoring] 启动调度器,匹配到', Object.keys(hostConfigs).length, '台设备');
|
||||
console.log('[monitoring] 启动调度器,匹配到', Object.keys(hostConfigs).length, '台设备(running=' + schedulerStatus.running + ')');
|
||||
// 不管 running 与否都传 hostConfigs 过去让主进程更新,
|
||||
// 主进程 execFn 通过模块级 _hostConfigs 引用动态读最新值
|
||||
await inspectionSchedulerApi.start(hostConfigs);
|
||||
setSchedulerRunning(true);
|
||||
}
|
||||
@@ -586,7 +644,7 @@ export function useMonitoringData(hosts: Host[], keys: any[] = []): UseMonitorin
|
||||
setOnlineMap((prev) => {
|
||||
const next: Record<string, boolean> = { ...prev };
|
||||
let changed = false;
|
||||
for (const h of hosts) {
|
||||
for (const h of effectiveHosts) {
|
||||
if (next[h.id] === undefined) {
|
||||
next[h.id] = true; // 假设在线,等巡检结果覆盖
|
||||
changed = true;
|
||||
@@ -594,7 +652,7 @@ export function useMonitoringData(hosts: Host[], keys: any[] = []): UseMonitorin
|
||||
}
|
||||
return changed ? next : prev;
|
||||
});
|
||||
}, [hosts]);
|
||||
}, [effectiveHosts]);
|
||||
|
||||
// 从告警规则派生阈值(和 alertRuleStore 默认规则对齐)
|
||||
const thresholds = useMemo(() => {
|
||||
@@ -636,5 +694,6 @@ export function useMonitoringData(hosts: Host[], keys: any[] = []): UseMonitorin
|
||||
thresholds,
|
||||
refresh,
|
||||
loading,
|
||||
effectiveHosts,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user