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:
@@ -1,8 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ArrowUpCircle, Check, Loader2, RefreshCcw } from "lucide-react";
|
||||
import { ArrowUpCircle, Check, Copy, Globe, Loader2, RefreshCcw, Server } from "lucide-react";
|
||||
import AppLogo from "./AppLogo";
|
||||
import AppWordmark from "./AppWordmark";
|
||||
import { Button } from "./ui/button";
|
||||
import { Switch } from "./ui/switch";
|
||||
import { useApplicationBackend } from "../application/state/useApplicationBackend";
|
||||
import type { UpdateState, UseUpdateCheckResult } from "../application/state/useUpdateCheck";
|
||||
import { useI18n } from "../application/i18n/I18nProvider";
|
||||
@@ -10,6 +11,7 @@ import { SettingsAnchor, SettingsTabContent } from "./settings/settings-ui";
|
||||
import { toast } from "./ui/toast";
|
||||
import { useStoredString } from "../application/state/useStoredString";
|
||||
import { STORAGE_KEY_MONITORING_DASHBOARD_TITLE, STORAGE_KEY_REPORT_PRODUCT_NAME } from "../infrastructure/config/storageKeys";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
type AppInfo = {
|
||||
name: string;
|
||||
@@ -36,6 +38,66 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
|
||||
const [monitoringTitle, setMonitoringTitle] = useStoredString<string>(STORAGE_KEY_MONITORING_DASHBOARD_TITLE, '云启Net智能巡检监测系统');
|
||||
const [reportProductName, setReportProductName] = useStoredString<string>(STORAGE_KEY_REPORT_PRODUCT_NAME, '');
|
||||
|
||||
// Electron 本地:启动时从主进程恢复 dashboardTitle / reportProductName
|
||||
// 解决绿色便携版首次启动 Local Storage 空时显示旧默认值的问题
|
||||
useEffect(() => {
|
||||
const IS_REMOTE = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
|
||||
if (IS_REMOTE) return;
|
||||
const nc = (window as any).netcatty;
|
||||
if (!nc?.inspection) return;
|
||||
|
||||
// 恢复大屏标题(如果 Local Storage 空或用的是默认值)
|
||||
nc.inspection.getDashboardTitle?.().then((v: string) => {
|
||||
if (v && v.length > 0 && v !== '云启Net智能巡检监测系统') {
|
||||
const local = localStorage.getItem(STORAGE_KEY_MONITORING_DASHBOARD_TITLE);
|
||||
if (!local || local === '云启Net智能巡检监测系统') {
|
||||
setMonitoringTitle(v);
|
||||
console.info('[Settings] restored monitoringTitle:', v);
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
// 恢复报告产品名
|
||||
nc.inspection.getReportProductName?.().then((v: string) => {
|
||||
if (v && v.length > 0) {
|
||||
const local = localStorage.getItem(STORAGE_KEY_REPORT_PRODUCT_NAME);
|
||||
if (!local || local === '') {
|
||||
setReportProductName(v);
|
||||
console.info('[Settings] restored reportProductName:', v);
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// 同步到主进程 inspection-data.json(远程浏览器 API 可读)
|
||||
useEffect(() => {
|
||||
try {
|
||||
const nc = (window as any).netcatty;
|
||||
if (monitoringTitle) nc?.inspection?.saveDashboardTitle?.(monitoringTitle).catch(() => {});
|
||||
nc?.inspection?.saveReportProductName?.(reportProductName || '').catch(() => {});
|
||||
} catch {}
|
||||
}, [monitoringTitle, reportProductName]);
|
||||
|
||||
// 远程 HTTP Server 状态
|
||||
const [remoteStatus, setRemoteStatus] = useState<{ running: boolean; port: number; token: string | null }>({
|
||||
running: false, port: 8848, token: null,
|
||||
});
|
||||
const [remoteLoading, setRemoteLoading] = useState(false);
|
||||
const [remotePortDraft, setRemotePortDraft] = useState("8848");
|
||||
|
||||
const loadRemoteStatus = async () => {
|
||||
try {
|
||||
const s = await (window as any).netcatty?.remoteHttp?.getStatus?.();
|
||||
if (s) {
|
||||
setRemoteStatus({ running: !!s.running, port: s.port || 8848, token: s.token || null });
|
||||
setRemotePortDraft(String(s.port || 8848));
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
useEffect(() => { void loadRemoteStatus(); }, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const load = async () => {
|
||||
@@ -99,6 +161,44 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
|
||||
setTimeout(() => setLastCheckResult('none'), 3000);
|
||||
};
|
||||
|
||||
// ─── 远程访问 handlers ───
|
||||
const handleToggleRemote = async (checked: boolean) => {
|
||||
setRemoteLoading(true);
|
||||
try {
|
||||
const port = parseInt(remotePortDraft, 10) || 8848;
|
||||
const res = await (window as any).netcatty?.remoteHttp?.configure?.(checked, port);
|
||||
if (res?.ok) {
|
||||
setRemoteStatus({
|
||||
running: !!checked,
|
||||
port: res.port || port,
|
||||
token: res.token || remoteStatus.token,
|
||||
});
|
||||
setRemotePortDraft(String(res.port || port));
|
||||
if (checked) toast.success("远程访问已启动");
|
||||
else toast.success("远程访问已停止");
|
||||
} else {
|
||||
toast.error(res?.error || "操作失败");
|
||||
await loadRemoteStatus();
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message || "操作失败");
|
||||
await loadRemoteStatus();
|
||||
} finally {
|
||||
setRemoteLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyUrl = async () => {
|
||||
if (!remoteStatus.token) { toast.error("请先启用远程访问"); return; }
|
||||
const url = `http://localhost:${remoteStatus.port}/dashboard?token=${remoteStatus.token}`;
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
toast.success("访问链接已复制");
|
||||
} catch {
|
||||
toast.error("复制失败");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsTabContent value="application">
|
||||
<div className="flex flex-col lg:flex-row gap-10 lg:gap-14">
|
||||
@@ -168,6 +268,65 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
|
||||
onCommit={(v) => setReportProductName(v)}
|
||||
/>
|
||||
</SettingsAnchor>
|
||||
|
||||
{/* 远程访问(浏览器 HTTP Server) */}
|
||||
<SettingsAnchor anchorId="application-remote-access" className="mt-6">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Server size={16} className="text-muted-foreground" />
|
||||
<label className="text-sm font-medium text-foreground">远程访问</label>
|
||||
{remoteStatus.running ? (
|
||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-medium bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" />
|
||||
运行中
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[10px] font-medium bg-muted text-muted-foreground">
|
||||
已停止
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Switch
|
||||
checked={remoteStatus.running}
|
||||
disabled={remoteLoading}
|
||||
onCheckedChange={handleToggleRemote}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-xs text-muted-foreground w-16 shrink-0">端口</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={remotePortDraft}
|
||||
onChange={(e) => setRemotePortDraft(e.target.value)}
|
||||
className="w-24 px-2 py-1 rounded-md border border-input bg-background text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground ml-2">(默认 8848,修改后需重启远程访问生效)</span>
|
||||
</div>
|
||||
|
||||
{remoteStatus.running && remoteStatus.token && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe size={14} className="text-muted-foreground shrink-0" />
|
||||
<code className="flex-1 px-2 py-1 rounded-md bg-muted text-xs text-foreground truncate select-all">
|
||||
http://localhost:{remoteStatus.port}/dashboard?token={remoteStatus.token}
|
||||
</code>
|
||||
<Button variant="outline" size="sm" onClick={handleCopyUrl} className="shrink-0">
|
||||
<Copy size={14} className="mr-1" /> 复制
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
启用后可在同一局域网内通过浏览器访问本机数据看板(设备列表、告警、巡检快照)。
|
||||
连接时需带上上方 token,建议仅在可信内网环境使用。
|
||||
</p>
|
||||
</div>
|
||||
</SettingsAnchor>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user