feat: 新增设备巡检模块实现完整的巡检告警功能

新增设备巡检模块核心功能,包括:
- 巡检数据存储与调度系统
- 华为/H3C交换机及Linux服务器采集器
- 设备扩展配置与告警规则管理
- 主机详情页巡检配置入口
- 产品名称自定义设置功能
- 网络设备SSH exec通道兼容处理
This commit is contained in:
2026-10-09 10:37:48 +08:00
parent 9202506c46
commit c64e00d835
75 changed files with 12087 additions and 446 deletions

View File

@@ -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 */}