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,9 +1,8 @@
import React, { useEffect, useMemo, useState } from "react";
import { ArrowUpCircle, Bug, Check, Github, Loader2, MessageCircle, Newspaper, RefreshCcw } from "lucide-react";
import React, { useEffect, useState } from "react";
import { ArrowUpCircle, Check, Loader2, RefreshCcw } from "lucide-react";
import AppLogo from "./AppLogo";
import AppWordmark from "./AppWordmark";
import { Button } from "./ui/button";
import { cn } from "../lib/utils";
import { useApplicationBackend } from "../application/state/useApplicationBackend";
import type { UpdateState, UseUpdateCheckResult } from "../application/state/useUpdateCheck";
import { useI18n } from "../application/i18n/I18nProvider";
@@ -16,75 +15,6 @@ type AppInfo = {
platform?: string;
};
const REPO_URL = "https://github.com/binaricat/Netcatty";
const BUG_REPORT_TEMPLATE = "bug_report.yml";
const mapIssuePlatform = (platform?: string) => {
switch (platform) {
case "darwin":
return "macOS";
case "win32":
return "Windows";
case "linux":
return "Linux";
default:
return undefined;
}
};
/** Opens GitHub's Bug Report issue form with fields prefilled from the running app. */
export const buildIssueUrl = (appInfo: AppInfo) => {
const params = new URLSearchParams({
template: BUG_REPORT_TEMPLATE,
title: "[Bug] ",
});
if (appInfo.version) {
params.set("version", appInfo.version);
}
const platform = mapIssuePlatform(appInfo.platform);
if (platform) {
params.set("platform", platform);
}
const installSource =
appInfo.version === "0.0.0"
? "Built from source (npm run dev / pack)"
: "GitHub Release (.dmg / .exe / .AppImage / .deb / .rpm / .pacman)";
params.set("install_source", installSource);
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "unknown";
params.set(
"logs",
`Reported from Netcatty Settings (${appInfo.name} ${appInfo.version || "unknown"}).\n\nUser-Agent: ${ua}`,
);
return `${REPO_URL}/issues/new?${params.toString()}`;
};
const ActionRow: React.FC<{
icon: React.ReactNode;
title: string;
subtitle: string;
onClick: () => void;
}> = ({ icon, title, subtitle, onClick }) => (
<button
type="button"
onClick={onClick}
className={cn(
"w-full flex items-center gap-3 rounded-lg px-3 py-3 text-left",
"hover:bg-muted/50 transition-colors"
)}
>
<div className="shrink-0 text-muted-foreground">{icon}</div>
<div className="min-w-0">
<div className="text-sm font-medium leading-tight">{title}</div>
<div className="text-xs text-muted-foreground mt-0.5 truncate">{subtitle}</div>
</div>
</button>
);
interface SettingsApplicationTabProps {
updateState: UpdateState;
checkNow: UseUpdateCheckResult['checkNow'];
@@ -92,9 +22,11 @@ interface SettingsApplicationTabProps {
installUpdate: UseUpdateCheckResult['installUpdate'];
startDownload: UseUpdateCheckResult['startDownload'];
isUpdateDemoMode: boolean;
productName: string;
setProductName: (value: string) => void;
}
export default function SettingsApplicationTab({ updateState, checkNow, openReleasePage, installUpdate, startDownload, isUpdateDemoMode }: SettingsApplicationTabProps) {
export default function SettingsApplicationTab({ updateState, checkNow, openReleasePage, installUpdate, startDownload, isUpdateDemoMode, productName, setProductName }: SettingsApplicationTabProps) {
const { t } = useI18n();
const { openExternal, getApplicationInfo } = useApplicationBackend();
const [appInfo, setAppInfo] = useState<AppInfo>({ name: "Netcatty", version: "" });
@@ -163,10 +95,6 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
setTimeout(() => setLastCheckResult('none'), 3000);
};
const issueUrl = useMemo(() => buildIssueUrl(appInfo), [appInfo]);
const releasesUrl = `${REPO_URL}/releases`;
const discussionsUrl = `${REPO_URL}/discussions`;
return (
<SettingsTabContent value="application">
<div className="flex flex-col lg:flex-row gap-10 lg:gap-14">
@@ -174,7 +102,7 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
<div className="flex items-center gap-4">
<AppLogo className="w-16 h-16" />
<div>
<AppWordmark accessibleLabel="Netcatty" className="h-8 w-auto text-foreground" />
<AppWordmark accessibleLabel={productName} className="h-8 w-auto text-foreground" />
<div className="flex items-center gap-2 mt-1">
<span className="text-sm text-muted-foreground">
{appInfo.version ? appInfo.version : " "}
@@ -224,43 +152,35 @@ export default function SettingsApplicationTab({ updateState, checkNow, openRele
}
</Button>
</SettingsAnchor>
</div>
<div className="flex-1">
<div className="space-y-2">
<SettingsAnchor anchorId="application-report-problem">
<ActionRow
icon={<Bug size={18} />}
title={t("settings.application.reportProblem")}
subtitle={t("settings.application.reportProblem.subtitle")}
onClick={() => void handleOpenExternal(issueUrl)}
{/* 产品名称配置 */}
<SettingsAnchor anchorId="application-product-name" className="mt-6">
<div className="space-y-1.5">
<label className="text-sm font-medium text-foreground">
{t('settings.application.productName') || 'Product Name'}
</label>
<input
type="text"
value={productName}
onChange={(e) => setProductName(e.target.value)}
onBlur={() => {
// 失焦时确保标题栏更新
const bridge = (window as any).netcatty;
bridge?.setWindowTitle?.(productName);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
(e.target as HTMLInputElement).blur();
}
}}
placeholder="NetMesh"
className="w-full px-3 py-2 rounded-lg border border-input bg-background text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/>
</SettingsAnchor>
<SettingsAnchor anchorId="application-community">
<ActionRow
icon={<MessageCircle size={18} />}
title={t("settings.application.community")}
subtitle={t("settings.application.community.subtitle")}
onClick={() => void handleOpenExternal(discussionsUrl)}
/>
</SettingsAnchor>
<SettingsAnchor anchorId="application-github">
<ActionRow
icon={<Github size={18} />}
title="GitHub"
subtitle={t("settings.application.github.subtitle")}
onClick={() => void handleOpenExternal(REPO_URL)}
/>
</SettingsAnchor>
<SettingsAnchor anchorId="application-whats-new">
<ActionRow
icon={<Newspaper size={18} />}
title={t("settings.application.whatsNew")}
subtitle={t("settings.application.whatsNew.subtitle")}
onClick={() => void handleOpenExternal(releasesUrl)}
/>
</SettingsAnchor>
</div>
<p className="text-xs text-muted-foreground">
{t('settings.application.productNameHint') || 'Used in title bar, sidebar, and settings'}
</p>
</div>
</SettingsAnchor>
</div>
</div>