feat: 新增设备巡检模块实现完整的巡检告警功能
新增设备巡检模块核心功能,包括: - 巡检数据存储与调度系统 - 华为/H3C交换机及Linux服务器采集器 - 设备扩展配置与告警规则管理 - 主机详情页巡检配置入口 - 产品名称自定义设置功能 - 网络设备SSH exec通道兼容处理
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, lazy, Suspense, useState, useEffect } from "react";
|
||||
import { AlertTriangle, Bell, Activity, Network } from "lucide-react";
|
||||
import { deleteVaultKey } from "../../application/defaultKeyPassphrases";
|
||||
import { usePluginImporterCommit } from "../../application/state/usePluginImporterCommit";
|
||||
import { preserveConcurrentHostLineTimestampUpdate } from "../../domain/host";
|
||||
@@ -9,7 +10,8 @@ import {
|
||||
collectVisibleVaultHostIds,
|
||||
retainVisibleVaultGroupSelection,
|
||||
} from "../../domain/vaultGroupSelection";
|
||||
import { STORAGE_KEY_VAULT_HOST_PANEL_WIDTH } from "@/infrastructure/config/storageKeys.ts";
|
||||
import { STORAGE_KEY_VAULT_HOST_PANEL_WIDTH, STORAGE_KEY_PRODUCT_NAME } from "@/infrastructure/config/storageKeys.ts";
|
||||
import { localStorageAdapter } from "@/infrastructure/persistence/localStorageAdapter.ts";
|
||||
import { VaultHostListSection } from "./VaultHostListSection";
|
||||
import { VaultImportProgressPanel } from "./ImportVaultDialog";
|
||||
import {
|
||||
@@ -23,6 +25,14 @@ import { useNotesStore } from "../../application/state/notesStore";
|
||||
import { LazyLoadBoundary } from "../ui/lazy-load-boundary";
|
||||
import { toast } from "../ui/toast";
|
||||
import { AppWordmark } from "../AppWordmark";
|
||||
import { InspectionErrorBoundary } from "../inspection/InspectionErrorBoundary";
|
||||
|
||||
// 巡检模块页面(自包含,通过 IPC 直接调用主进程)
|
||||
const AlertCenterPage = lazy(() => import("../inspection/AlertCenterPage"));
|
||||
const AlertRulesPage = lazy(() => import("../inspection/AlertRulesPage"));
|
||||
const InspectionCenterPage = lazy(() => import("../inspection/InspectionCenterPage"));
|
||||
// 运行监控 - 3D 拓扑大屏
|
||||
const MonitoringTopologyView = lazy(() => import("../monitoring/MonitoringTopologyView"));
|
||||
|
||||
type VaultViewLayoutContext = Record<string, any>;
|
||||
|
||||
@@ -123,6 +133,27 @@ function VaultConnectionLogsSection({
|
||||
}
|
||||
|
||||
export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
// 产品名称 - 直接读取 localStorage,监听 storage 事件(来自其他标签页)和自定义事件
|
||||
const [productName, setProductName] = useState(() => localStorageAdapter.readString(STORAGE_KEY_PRODUCT_NAME) || 'NetMesh');
|
||||
|
||||
// 监听 productName 变化
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
const newName = localStorageAdapter.readString(STORAGE_KEY_PRODUCT_NAME) || 'NetMesh';
|
||||
console.log('[VaultViewLayout] productName changed to:', newName);
|
||||
setProductName(newName);
|
||||
};
|
||||
// 监听 storage 事件(来自其他标签页)
|
||||
window.addEventListener('storage', handler);
|
||||
// 监听自定义事件(来自同一标签页的设置页面)
|
||||
window.addEventListener('productNameChanged' as any, handler);
|
||||
console.log('[VaultViewLayout] productName listener mounted, current:', productName);
|
||||
return () => {
|
||||
window.removeEventListener('storage', handler);
|
||||
window.removeEventListener('productNameChanged' as any, handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const {
|
||||
Activity,
|
||||
allGroupPaths,
|
||||
@@ -550,7 +581,7 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
>
|
||||
<AppLogo className="h-8 w-8 flex-shrink-0" />
|
||||
{!sidebarCollapsed && (
|
||||
<AppWordmark className="h-5 w-auto text-foreground" />
|
||||
<span className="text-sm font-medium">{productName}</span>
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
@@ -772,6 +803,105 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{/* 分隔线 */}
|
||||
<div className="h-px bg-border/60 my-2" />
|
||||
|
||||
{/* 巡检中心 - inspection */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
variant={currentSection === "inspection" ? "secondary" : "ghost"}
|
||||
className={cn(
|
||||
"w-full h-10",
|
||||
sidebarCollapsed
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
currentSection === "inspection" &&
|
||||
"bg-foreground/10 text-foreground hover:bg-foreground/15 border-border/40",
|
||||
)}
|
||||
onClick={() => setCurrentSection("inspection")}
|
||||
>
|
||||
<Activity size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && "巡检中心"}
|
||||
</RippleButton>
|
||||
</TooltipTrigger>
|
||||
{sidebarCollapsed && (
|
||||
<TooltipContent side="right">巡检中心</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{/* 告警中心 - alerts */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
variant={currentSection === "alerts" ? "secondary" : "ghost"}
|
||||
className={cn(
|
||||
"w-full h-10",
|
||||
sidebarCollapsed
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
currentSection === "alerts" &&
|
||||
"bg-foreground/10 text-foreground hover:bg-foreground/15 border-border/40",
|
||||
)}
|
||||
onClick={() => setCurrentSection("alerts")}
|
||||
>
|
||||
<Bell size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && "告警中心"}
|
||||
</RippleButton>
|
||||
</TooltipTrigger>
|
||||
{sidebarCollapsed && (
|
||||
<TooltipContent side="right">告警中心</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{/* 告警规则 - alertRules */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
variant={currentSection === "alertRules" ? "secondary" : "ghost"}
|
||||
className={cn(
|
||||
"w-full h-10",
|
||||
sidebarCollapsed
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
currentSection === "alertRules" &&
|
||||
"bg-foreground/10 text-foreground hover:bg-foreground/15 border-border/40",
|
||||
)}
|
||||
onClick={() => setCurrentSection("alertRules")}
|
||||
>
|
||||
<AlertTriangle size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && "告警规则"}
|
||||
</RippleButton>
|
||||
</TooltipTrigger>
|
||||
{sidebarCollapsed && (
|
||||
<TooltipContent side="right">告警规则</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{/* 运行监控 - monitoring (3D 拓扑大屏) */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<RippleButton
|
||||
variant={currentSection === "monitoring" ? "secondary" : "ghost"}
|
||||
className={cn(
|
||||
"w-full h-10",
|
||||
sidebarCollapsed
|
||||
? "justify-center p-0"
|
||||
: "justify-start gap-3",
|
||||
currentSection === "monitoring" &&
|
||||
"bg-foreground/10 text-foreground hover:bg-foreground/15 border-border/40",
|
||||
)}
|
||||
onClick={() => setCurrentSection("monitoring")}
|
||||
>
|
||||
<Network size={16} className="flex-shrink-0" />
|
||||
{!sidebarCollapsed && "运行监控"}
|
||||
</RippleButton>
|
||||
</TooltipTrigger>
|
||||
{sidebarCollapsed && (
|
||||
<TooltipContent side="right">运行监控</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -1402,6 +1532,82 @@ export function VaultViewLayout({ ctx }: { ctx: VaultViewLayoutContext }) {
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
)}
|
||||
|
||||
{/* 巡检中心 */}
|
||||
{currentSection === "inspection" && (
|
||||
<LazyLoadBoundary name="Inspection center" resetKey="inspection-center">
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<InspectionErrorBoundary>
|
||||
<InspectionCenterPage hosts={ctx.hosts} keys={ctx.keys} />
|
||||
</InspectionErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
)}
|
||||
|
||||
{/* 告警中心 */}
|
||||
{currentSection === "alerts" && (
|
||||
<LazyLoadBoundary name="Alert center" resetKey="alert-center">
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<InspectionErrorBoundary>
|
||||
<AlertCenterPage />
|
||||
</InspectionErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
)}
|
||||
|
||||
{/* 告警规则配置 */}
|
||||
{currentSection === "alertRules" && (
|
||||
<LazyLoadBoundary name="Alert rules" resetKey="alert-rules">
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<InspectionErrorBoundary>
|
||||
<AlertRulesPage />
|
||||
</InspectionErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
)}
|
||||
|
||||
{/* 运行监控 - 3D 拓扑大屏 */}
|
||||
{currentSection === "monitoring" && (
|
||||
<LazyLoadBoundary
|
||||
name="Monitoring topology"
|
||||
resetKey="monitoring-topology"
|
||||
fallback={(error) => (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<div className="text-base font-medium text-foreground">Monitoring topology could not load.</div>
|
||||
{error && (
|
||||
<div className="w-full max-w-xl overflow-auto rounded border border-red-500/30 bg-red-500/5 p-3 text-left text-xs text-red-400">
|
||||
<div className="mb-1 font-semibold">Error: {error.message}</div>
|
||||
<pre className="whitespace-pre-wrap font-mono text-[10px] opacity-80">
|
||||
{error.stack}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-muted"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<Suspense fallback={<VaultSectionLoading />}>
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<MonitoringTopologyView
|
||||
hosts={ctx.hosts}
|
||||
keys={ctx.keys}
|
||||
/>
|
||||
</div>
|
||||
</Suspense>
|
||||
</LazyLoadBoundary>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Group Details Panel */}
|
||||
|
||||
Reference in New Issue
Block a user