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

巡检模块:巡检调度器改为轮询驱动,新增每日报告存储、告警规则独立
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:
2026-10-19 21:11:57 +08:00
committed by trae-solo-agent
parent 336c3afd47
commit 2e71e03d71
34 changed files with 3012 additions and 230 deletions

View File

@@ -5,6 +5,7 @@ import {
Clock,
RefreshCw,
Play,
Pause,
Search,
Server,
ListIcon,
@@ -40,6 +41,7 @@ import {
inspectionResultApi,
inspectionExecApi,
inspectionAlertApi,
inspectionSchedulerApi,
type DeviceExtension,
type DeviceCatalog,
type InspectionResult,
@@ -72,6 +74,7 @@ interface DeviceRow {
export function InspectionCenterPage() {
const { hosts, keys } = useVaultState();
const IS_REMOTE = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
const [extensions, setExtensions] = useState<Record<string, DeviceExtension>>({});
const [catalogs, setCatalogs] = useState<DeviceCatalog[]>([]);
const [latestResults, setLatestResults] = useState<Record<string, InspectionResult>>({});
@@ -79,6 +82,10 @@ export function InspectionCenterPage() {
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
// 批量启用/停止巡检状态
const [batchEnabling, setBatchEnabling] = useState(false);
const [batchStopping, setBatchStopping] = useState(false);
// 筛选
const [searchText, setSearchText] = useState("");
const [filterCatalog, setFilterCatalog] = useState<string>("all");
@@ -128,6 +135,33 @@ export function InspectionCenterPage() {
}
}, []);
// 批量启用/停止(必须在 loadData 之后声明,避免 TDZ)
const handleBatchEnable = useCallback(async () => {
setBatchEnabling(true);
try {
const res = await inspectionDeviceApi.batchSetEnabled(true);
toast.success(`已批量启用 ${res.count} 台设备自动巡检`);
await loadData();
} catch (e: any) {
toast.error(e?.message || "操作失败");
} finally {
setBatchEnabling(false);
}
}, [loadData]);
const handleBatchStop = useCallback(async () => {
setBatchStopping(true);
try {
const res = await inspectionDeviceApi.batchSetEnabled(false);
toast.success(`已批量停止 ${res.count} 台设备自动巡检`);
await loadData();
} catch (e: any) {
toast.error(e?.message || "操作失败");
} finally {
setBatchStopping(false);
}
}, [loadData]);
useEffect(() => {
loadData();
}, [loadData]);
@@ -164,11 +198,33 @@ export function InspectionCenterPage() {
};
}, [loadData]);
// 远程浏览器模式下,用定时轮询替代主进程事件推送(每 10 秒自动刷新)
useEffect(() => {
if (!IS_REMOTE) return;
const interval = setInterval(() => {
void loadData(true);
}, 10000);
return () => clearInterval(interval);
}, [IS_REMOTE, loadData]);
// 构建设备行数据
const deviceRows = useMemo<DeviceRow[]>(() => {
return (hosts || [])
.filter((h) => h && h.id)
.map((host) => {
// 远程浏览器:vault hosts 不可用,从 inspection extensions + 巡检结果合成虚拟 hosts
const effectiveHosts = (hosts && hosts.length > 0)
? hosts
: IS_REMOTE
? Object.values(extensions).map((ext: any) => ({
id: ext.host_id,
label: ext.label || ext.hostname || ext.host_id,
hostname: ext.hostname || "",
address: ext.hostname || "",
platform: "ssh",
}))
: (hosts || []);
return effectiveHosts
.filter((h: any) => h && h.id)
.map((host: any) => {
const ext = extensions[host.id];
const catalog = catalogs.find((c) => c.id === ext?.catalog_id);
const lastResult = latestResults[host.id];
@@ -390,10 +446,12 @@ export function InspectionCenterPage() {
</p>
</div>
</div>
<Button variant="outline" onClick={loadData} disabled={refreshing}>
<RefreshCw size={14} className={`mr-2 ${refreshing ? "animate-spin" : ""}`} />
刷新
</Button>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={loadData} disabled={refreshing}>
<RefreshCw size={14} className={`mr-2 ${refreshing ? "animate-spin" : ""}`} />
刷新
</Button>
</div>
</div>
{/* 统计卡片 */}
@@ -499,7 +557,32 @@ export function InspectionCenterPage() {
</div>
{/* 设备列表 */}
<div className="flex-1 overflow-auto px-6 py-4">
<div className="flex items-center justify-between px-6 pt-3 pb-2">
<div className="text-sm text-muted-foreground">
共 <span className="font-medium text-foreground">{filteredRows.length}</span> 台设备
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={handleBatchEnable}
disabled={batchEnabling || loading}
>
{batchEnabling ? <RefreshCw size={12} className="mr-1 animate-spin" /> : <Play size={12} className="mr-1" />}
全部启用巡检
</Button>
<Button
variant="outline"
size="sm"
onClick={handleBatchStop}
disabled={batchStopping || loading}
>
{batchStopping ? <RefreshCw size={12} className="mr-1 animate-spin" /> : <Pause size={12} className="mr-1" />}
全部停止巡检
</Button>
</div>
</div>
<div className="flex-1 overflow-auto px-6 pb-4">
{loading ? (
<div className="text-center text-muted-foreground py-12">加载中...</div>
) : filteredRows.length === 0 ? (