feat: add monitoring settings tab, workbench view, and inspection alert improvements
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
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useCallback, lazy, Suspense, useState, useEffect } from "react";
|
||||
import { AlertTriangle, Bell, Activity, Network, FileText } from "lucide-react";
|
||||
import { AlertTriangle, Bell, Activity, Network, FileText, BarChart3 } from "lucide-react";
|
||||
import { WorkbenchView } from "./WorkbenchView";
|
||||
import { deleteVaultKey } from "../../application/defaultKeyPassphrases";
|
||||
import { usePluginImporterCommit } from "../../application/state/usePluginImporterCommit";
|
||||
import { preserveConcurrentHostLineTimestampUpdate } from "../../domain/host";
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
} from "./VaultPageHeader";
|
||||
import { useConnectionLogsStore } from "../../application/state/connectionLogsStore";
|
||||
import { useNotesStore } from "../../application/state/notesStore";
|
||||
import { useSettingsState } from "../../application/state/useSettingsState";
|
||||
import { LazyLoadBoundary } from "../ui/lazy-load-boundary";
|
||||
import { toast } from "../ui/toast";
|
||||
import { AppWordmark } from "../AppWordmark";
|
||||
@@ -35,6 +37,8 @@ const InspectionCenterPage = lazy(() => import("../inspection/InspectionCenterPa
|
||||
const MonitoringTopologyView = lazy(() => import("../monitoring/MonitoringTopologyView"));
|
||||
// 巡检报告
|
||||
const ReportPage = lazy(() => import("../inspection/ReportPage"));
|
||||
// 设置页面(远程访问降级用)
|
||||
const SettingsPage = lazy(() => import("../SettingsPage"));
|
||||
|
||||
type VaultViewLayoutContext = Record<string, any>;
|
||||
|
||||
@@ -111,10 +115,12 @@ function VaultConnectionLogsSection({
|
||||
ConnectionLogsManager,
|
||||
hosts,
|
||||
onOpenLogView,
|
||||
initialLogId,
|
||||
}: {
|
||||
ConnectionLogsManager: React.ComponentType<any>;
|
||||
hosts: any[];
|
||||
onOpenLogView: (log: any) => void;
|
||||
initialLogId?: string;
|
||||
}) {
|
||||
const {
|
||||
connectionLogs,
|
||||
@@ -122,6 +128,14 @@ function VaultConnectionLogsSection({
|
||||
deleteConnectionLog,
|
||||
clearUnsavedConnectionLogs,
|
||||
} = useConnectionLogsStore();
|
||||
|
||||
// 从工作台跳转时自动打开指定 log 的详情
|
||||
React.useEffect(() => {
|
||||
if (!initialLogId) return;
|
||||
const log = connectionLogs.find((l: any) => l.id === initialLogId);
|
||||
if (log) onOpenLogView(log);
|
||||
}, [initialLogId, connectionLogs, onOpenLogView]);
|
||||
|
||||
return (
|
||||
<ConnectionLogsManager
|
||||
logs={connectionLogs}
|
||||
@@ -137,6 +151,34 @@ function VaultConnectionLogsSection({
|
||||
export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
const IS_REMOTE = typeof window !== "undefined" && !!(window as any).__REMOTE_TOKEN__;
|
||||
|
||||
// ─── 设置权威源同步(远程浏览器) ───
|
||||
// 1. HTML 注入脚本里的同步 XHR 已在 React 启动前把权威设置填到 localStorage
|
||||
// 2. 这里注册写后钩子:每次 localStorageAdapter 写都 POST 回权威源
|
||||
React.useEffect(() => {
|
||||
if (!IS_REMOTE) return;
|
||||
const TOKEN = (window as any).__REMOTE_TOKEN__;
|
||||
const API_BASE = `${window.location.origin}`;
|
||||
|
||||
(globalThis as any).__NETCATTY_SETTINGS_SYNC__ = (key: string, value: string | null) => {
|
||||
fetch(`${API_BASE}/api/settings?token=${encodeURIComponent(TOKEN)}`, {
|
||||
method: value === null ? "DELETE" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: value === null ? JSON.stringify({ keys: [key] }) : JSON.stringify({ [key]: value }),
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
return () => {
|
||||
if ((globalThis as any).__NETCATTY_SETTINGS_SYNC__) {
|
||||
(globalThis as any).__NETCATTY_SETTINGS_SYNC__ = null;
|
||||
}
|
||||
};
|
||||
}, [IS_REMOTE]);
|
||||
|
||||
const remoteSettings = useSettingsState();
|
||||
|
||||
// 工作台跳转时带过来的预选中项(alertId / logId / hostId)
|
||||
const pendingSelectionRef = React.useRef<{ alertId?: string; logId?: string; hostId?: string } | null>(null);
|
||||
|
||||
// 产品名称 - 直接读取 localStorage,监听 storage 事件(来自其他标签页)和自定义事件
|
||||
const [productName, setProductName] = useState(() => localStorageAdapter.readString(STORAGE_KEY_PRODUCT_NAME) || 'NetMesh');
|
||||
|
||||
@@ -444,6 +486,16 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
),
|
||||
[displayedGroups, viewMode, visibleTreeGroupPaths],
|
||||
);
|
||||
// 当切到 hosts section 且带了 hostId,自动选中
|
||||
React.useEffect(() => {
|
||||
if (currentSection !== "hosts") return;
|
||||
const pending = pendingSelectionRef.current;
|
||||
if (pending?.hostId) {
|
||||
setSelectedHostIds(new Set([pending.hostId]));
|
||||
pendingSelectionRef.current = null; // 消费掉
|
||||
}
|
||||
}, [currentSection, setSelectedHostIds]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedGroupPaths((current: Set<string>) => {
|
||||
const next = retainVisibleVaultGroupSelection(current, visibleSelectableGroupPaths);
|
||||
@@ -457,6 +509,7 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
resizeAriaLabel: t("vault.panel.resizeWidth"),
|
||||
};
|
||||
const [isSidebarResizing, setIsSidebarResizing] = React.useState(false);
|
||||
const [showRemoteSettings, setShowRemoteSettings] = React.useState(false);
|
||||
const keyListRef = React.useRef(keys);
|
||||
keyListRef.current = keys;
|
||||
const newHostActionsRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -600,6 +653,30 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
<div
|
||||
className={cn("space-y-1", sidebarCollapsed ? "px-1.5" : "px-2.5")}
|
||||
>
|
||||
{/* 工作台 */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
variant={currentSection === "workbench" ? "secondary" : "ghost"}
|
||||
className={cn(
|
||||
"w-full h-10",
|
||||
sidebarCollapsed
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
currentSection === "workbench" &&
|
||||
"bg-foreground/10 text-foreground hover:bg-foreground/15 border-border/40",
|
||||
)}
|
||||
onClick={() => setCurrentSection("workbench")}
|
||||
>
|
||||
<BarChart3 size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && "工作台"}
|
||||
</RippleButton>
|
||||
</TooltipTrigger>
|
||||
{sidebarCollapsed && (
|
||||
<TooltipContent side="right">工作台</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
@@ -948,7 +1025,13 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
)}
|
||||
onClick={onOpenSettings}
|
||||
onClick={() => {
|
||||
if (IS_REMOTE) {
|
||||
setShowRemoteSettings(true);
|
||||
} else {
|
||||
onOpenSettings();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Settings size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && t("common.settings")}
|
||||
@@ -1383,6 +1466,27 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 工作台 */}
|
||||
{currentSection === "workbench" && (
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<WorkbenchView
|
||||
hosts={hosts}
|
||||
keys={keys}
|
||||
proxies={[]}
|
||||
snippetCount={snippets?.length ?? 0}
|
||||
activePortForwardCount={0}
|
||||
onNavigate={(section: string, opts?: any) => {
|
||||
if (opts) pendingSelectionRef.current = opts;
|
||||
setCurrentSection(section);
|
||||
}}
|
||||
onConnectHost={(hostId: string) => {
|
||||
const h = hosts?.find((x: any) => x.id === hostId);
|
||||
if (h) handleHostConnect(h);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentSection === "snippets" && (
|
||||
<LazyLoadBoundary name="Snippets" resetKey="snippets">
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
@@ -1556,6 +1660,7 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
ConnectionLogsManager={LazyConnectionLogsManager}
|
||||
hosts={hosts}
|
||||
onOpenLogView={onOpenLogView}
|
||||
initialLogId={pendingSelectionRef.current?.logId ?? undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
@@ -1580,7 +1685,7 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<InspectionErrorBoundary>
|
||||
<AlertCenterPage />
|
||||
<AlertCenterPage initialAlertId={pendingSelectionRef.current?.alertId ?? undefined} />
|
||||
</InspectionErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
@@ -2028,6 +2133,25 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
onUpdateHosts([...hosts, host]);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Remote Settings Dialog (远程访问设置面板) */}
|
||||
{IS_REMOTE && (
|
||||
<Dialog open={showRemoteSettings} onOpenChange={setShowRemoteSettings}>
|
||||
<DialogContent className="max-w-5xl max-h-[92vh] overflow-auto p-0 gap-0">
|
||||
<DialogHeader className="px-6 pt-5 pb-3 border-b border-border">
|
||||
<DialogTitle>{t("common.settings")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Suspense fallback={<div className="p-8 text-center text-muted-foreground">加载设置中...</div>}>
|
||||
<SettingsPage
|
||||
settings={remoteSettings as any}
|
||||
appLock={{ enabled: false, locked: false } as any}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user