"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "next/navigation"; import { LandingNav } from "@/components/landing/LandingNav"; import { fetchAgentDownloadCatalog, formatSize, type AgentDownloadCatalog, type JreDisplayEntry, type NativeAgentDisplayEntry, type OfflineBundleEntry } from "@/lib/agentRegistry"; import { AlertTriangle, Archive, Cpu, Database, Download, Loader2, Plug, Search, Terminal, X } from "lucide-react"; const i18n = { en: { title: "Offline Driver Downloads", subtitle: "Download database drivers and JRE packages for offline use. Search for the exact resource your air-gapped environment needs.", jdbcPlugin: "JDBC Plugin", jdbcPluginDesc: "Install this optional DBX sidecar before using custom JDBC connections. Database vendor JDBC driver JARs still need to be imported separately.", jdbcPluginFile: "Plugin package", jdbcPluginInstallHint: "Import this ZIP in DBX from Settings > Driver Manager > JDBC Drivers > Local Install.", bundles: "Offline Bundles", bundlesDesc: "Platform-specific ZIP packages that include the agent registry, database drivers, native agents, and the matching JRE.", drivers: "Database Drivers", driversDesc: "JDBC driver JAR files for each supported database type.", nativeAgents: "Native Agents", nativeAgentsDesc: "Go-based native agents for Oracle and XuguDB. Download the executable that matches the offline machine.", jre: "Java Runtime (JRE)", jreDesc: "JRE packages used by agent-based database drivers. Required for Oracle, SQL Server, and other agent-managed connections.", loading: "Loading driver catalog...", error: "Unable to load driver catalog. Please check your network connection.", retry: "Retry", download: "Download", installMethod: "Install", version: "Version", size: "Size", requiresJre: "Requires JRE", platform: "Platform", filename: "File", search: "Search drivers, platforms, versions...", noResults: "No matching downloads.", showing: "Showing", of: "of", clearSearch: "Clear search", downloadHint: "For air-gapped environments: download the bundle for your platform on an internet-connected machine, then transfer it to the offline machine and import it in DBX from Settings > Driver Manager. Use the driver and JRE tabs only when you need individual artifacts.", }, cn: { title: "离线驱动下载", subtitle: "下载数据库驱动和 JRE 离线包。搜索内网环境需要的资源,在有网机器下载后传输。", jdbcPlugin: "JDBC 插件", jdbcPluginDesc: "使用自定义 JDBC 连接前先安装这个 DBX 可选插件。数据库厂商的 JDBC Driver JAR 仍需单独导入。", jdbcPluginFile: "插件包", jdbcPluginInstallHint: "在 DBX 的“设置 > 驱动管理 > JDBC 驱动 > 本地安装”中导入这个 ZIP。", bundles: "整包下载", bundlesDesc: "按平台提供的 ZIP 离线包,包含 Agent registry、数据库驱动、原生 Agent 和匹配的 JRE。", drivers: "数据库驱动", driversDesc: "每种支持的数据库类型对应的 JDBC 驱动 JAR 文件。", nativeAgents: "原生 Agent", nativeAgentsDesc: "Oracle 和虚谷使用 Go 原生 Agent,请下载与内网机器平台匹配的可执行文件。", jre: "Java 运行时 (JRE)", jreDesc: "Agent 驱动所需的 JRE 环境,Oracle、SQL Server 等数据库通过 Agent 连接时需要。", loading: "正在加载驱动列表...", error: "加载驱动列表失败,请检查网络连接。", retry: "重试", download: "下载", installMethod: "安装方式", version: "版本", size: "大小", requiresJre: "依赖 JRE", platform: "平台", filename: "文件", search: "搜索驱动、平台、版本...", noResults: "没有匹配的下载项。", showing: "显示", of: "/", clearSearch: "清空搜索", downloadHint: "内网环境使用说明:在有网的电脑上下载对应平台的整包,然后传输到内网机器,在 DBX 的“设置 > 驱动管理”中导入。只有需要单个产物时再使用驱动和 JRE 标签页。", }, }; type ActiveTab = "bundles" | "drivers" | "native" | "jre" | "jdbcPlugin"; function platformKey(j: JreDisplayEntry): string { return `${j.jreKey}-${j.platformKey}`; } function bundleKey(bundle: OfflineBundleEntry): string { return `${bundle.platformKey}-${bundle.filename}`; } function nativeKey(agent: NativeAgentDisplayEntry): string { return `${agent.key}-${agent.platformKey}`; } type NativeAgentGroup = { key: string; label: string; version: string; options: NativeAgentDisplayEntry[]; }; function matchesSearch(values: Array, query: string): boolean { if (!query) return true; return values.filter(Boolean).join(" ").toLowerCase().includes(query); } export function DriversClient() { const params = useParams(); const rawLang = params?.lang as string | undefined; const lang: "en" | "cn" = rawLang === "cn" ? "cn" : "en"; const t = i18n[lang]; const [catalog, setCatalog] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState("bundles"); const [searchQuery, setSearchQuery] = useState(""); const [selectedNativePlatforms, setSelectedNativePlatforms] = useState>({}); const loadCatalog = useCallback(async () => { setLoading(true); setError(null); try { const data = await fetchAgentDownloadCatalog(); if (data) { setCatalog(data); } else { setError("Unable to load driver catalog"); } } catch { setError("Unable to load driver catalog"); } finally { setLoading(false); } }, []); useEffect(() => { loadCatalog(); }, [loadCatalog]); const bundles = catalog?.bundles ?? []; const drivers = catalog?.drivers ?? []; const nativeAgents = catalog?.nativeAgents ?? []; const jres = catalog?.jres ?? []; const jdbcPlugin = catalog?.jdbcPlugin; const normalizedSearch = searchQuery.trim().toLowerCase(); const filteredBundles = useMemo(() => bundles.filter((bundle) => matchesSearch([bundle.platformLabel, bundle.platformKey, bundle.filename, formatSize(bundle.size)], normalizedSearch)), [bundles, normalizedSearch]); const filteredDrivers = useMemo(() => drivers.filter((d) => matchesSearch([d.label, d.key, d.version, d.jre, formatSize(d.jar.size)], normalizedSearch)), [drivers, normalizedSearch]); const nativeGroups = useMemo(() => { const groups = new Map(); for (const agent of nativeAgents) { const group = groups.get(agent.key); if (group) { group.options.push(agent); } else { groups.set(agent.key, { key: agent.key, label: agent.label, version: agent.version, options: [agent] }); } } return Array.from(groups.values()); }, [nativeAgents]); useEffect(() => { if (nativeGroups.length === 0) return; setSelectedNativePlatforms((current) => { const next = { ...current }; let changed = false; for (const group of nativeGroups) { if (group.options.length === 0) continue; if (!next[group.key] || !group.options.some((option) => option.platformKey === next[group.key])) { next[group.key] = group.options[0].platformKey; changed = true; } } return changed ? next : current; }); }, [nativeGroups]); const filteredNativeGroups = useMemo( () => nativeGroups.filter((group) => matchesSearch( [ group.label, group.key, group.version, ...group.options.flatMap((option) => [option.platformLabel, option.platformKey, option.filename, formatSize(option.info.size)]), ], normalizedSearch, ), ), [nativeGroups, normalizedSearch], ); const filteredJres = useMemo(() => jres.filter((j) => matchesSearch([j.platformLabel, j.platformKey, j.jreVersion, j.jreKey, formatSize(j.info.size)], normalizedSearch)), [jres, normalizedSearch]); const filteredJdbcPlugin = useMemo(() => (jdbcPlugin && matchesSearch([jdbcPlugin.label, jdbcPlugin.filename, jdbcPlugin.url, t.jdbcPlugin, t.jdbcPluginDesc], normalizedSearch) ? [jdbcPlugin] : []), [jdbcPlugin, normalizedSearch, t.jdbcPlugin, t.jdbcPluginDesc]); const activeCount = activeTab === "bundles" ? filteredBundles.length : activeTab === "drivers" ? filteredDrivers.length : activeTab === "native" ? filteredNativeGroups.length : activeTab === "jre" ? filteredJres.length : filteredJdbcPlugin.length; const activeTotal = activeTab === "bundles" ? bundles.length : activeTab === "drivers" ? drivers.length : activeTab === "native" ? nativeGroups.length : activeTab === "jre" ? jres.length : jdbcPlugin ? 1 : 0; return (

{t.subtitle}

{loading && (
{t.loading}
)} {error && !loading && (
{t.error}
)} {catalog && !loading && ( <>
setSearchQuery(event.target.value)} placeholder={t.search} className="h-9 w-full rounded-[8px] border border-landing-line bg-black/10 pl-9 pr-9 text-sm text-landing-ink outline-none transition-colors placeholder:text-landing-muted focus:border-landing-blue" /> {searchQuery && ( )}
{t.showing} {activeCount} {t.of} {activeTotal}
{activeTab === "jdbcPlugin" && ( <>

{t.jdbcPluginDesc}

{filteredJdbcPlugin.map((plugin) => ( ))}
{t.jdbcPluginFile} {t.filename} {t.installMethod}
{plugin.label} ZIP

{t.jdbcPluginInstallHint}

{plugin.filename} {t.jdbcPluginInstallHint} {t.download}
{filteredJdbcPlugin.length === 0 &&
{t.noResults}
} )} {activeTab === "bundles" && ( <>

{t.bundlesDesc}

{filteredBundles.map((bundle) => ( ))}
{t.platform} {t.filename} {t.size}
{bundle.platformLabel} ZIP
{bundle.filename} {formatSize(bundle.size)} {t.download}
{filteredBundles.length === 0 &&
{t.noResults}
} )} {activeTab === "drivers" && ( <>

{t.driversDesc}

{filteredDrivers.map((d) => ( ))}
Driver Key {t.version} {t.requiresJre} {t.size}
{d.label} {d.key}
{d.key} {d.version} {d.jre} {formatSize(d.jar.size)} {t.download}
{filteredDrivers.length === 0 &&
{t.noResults}
} )} {activeTab === "native" && ( <>

{t.nativeAgentsDesc}

{filteredNativeGroups.map((group) => { const selectedPlatform = selectedNativePlatforms[group.key] ?? group.options[0]?.platformKey; const selectedAgent = group.options.find((option) => option.platformKey === selectedPlatform) ?? group.options[0]; if (!selectedAgent) return null; return ( ); })}
Agent {t.platform} {t.version} {t.size}
{group.label} {selectedAgent.platformKey}
{group.version} {formatSize(selectedAgent.info.size)} {t.download}
{filteredNativeGroups.length === 0 &&
{t.noResults}
} )} {activeTab === "jre" && ( <>

{t.jreDesc}

{filteredJres.map((j) => { const key = platformKey(j); return ( ); })}
{t.platform} JRE {t.version} {t.size}
{j.platformLabel} JRE {j.jreKey}
JRE {j.jreKey} {j.jreVersion} {formatSize(j.info.size)} {t.download}
{filteredJres.length === 0 &&
{t.noResults}
} )}
{lang === "cn" ? "离线使用说明" : "Offline Usage"}

{t.downloadHint}

)}
); }