feat(docs): add JDBC plugin offline download

This commit is contained in:
t8y2 2026-07-06 14:34:58 +08:00
parent e84fb12fcc
commit c819885d2e
3 changed files with 88 additions and 4 deletions

View File

@ -4,12 +4,16 @@ 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, Search, Terminal, X } from "lucide-react";
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",
@ -22,6 +26,7 @@ const i18n = {
error: "Unable to load driver catalog. Please check your network connection.",
retry: "Retry",
download: "Download",
installMethod: "Install",
version: "Version",
size: "Size",
requiresJre: "Requires JRE",
@ -37,6 +42,10 @@ const i18n = {
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: "数据库驱动",
@ -49,6 +58,7 @@ const i18n = {
error: "加载驱动列表失败,请检查网络连接。",
retry: "重试",
download: "下载",
installMethod: "安装方式",
version: "版本",
size: "大小",
requiresJre: "依赖 JRE",
@ -63,7 +73,7 @@ const i18n = {
},
};
type ActiveTab = "bundles" | "drivers" | "native" | "jre";
type ActiveTab = "bundles" | "drivers" | "native" | "jre" | "jdbcPlugin";
function platformKey(j: JreDisplayEntry): string {
return `${j.jreKey}-${j.platformKey}`;
@ -127,6 +137,7 @@ export function DriversClient() {
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]);
@ -179,9 +190,10 @@ export function DriversClient() {
);
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 : filteredJres.length;
const activeTotal = activeTab === "bundles" ? bundles.length : activeTab === "drivers" ? drivers.length : activeTab === "native" ? nativeGroups.length : jres.length;
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 (
<main className="landing">
@ -242,6 +254,10 @@ export function DriversClient() {
<Cpu size={14} />
{t.jre}
</button>
<button type="button" onClick={() => setActiveTab("jdbcPlugin")} className={`inline-flex h-8 cursor-pointer items-center justify-center gap-2 rounded-[6px] px-3 text-xs font-[650] transition-colors ${activeTab === "jdbcPlugin" ? "bg-landing-blue text-white" : "text-landing-muted hover:text-landing-ink"}`}>
<Plug size={14} />
{t.jdbcPlugin}
</button>
</div>
<div className="flex min-w-[280px] flex-1 items-center gap-3 max-[760px]:min-w-full">
@ -265,6 +281,44 @@ export function DriversClient() {
</div>
</div>
{activeTab === "jdbcPlugin" && (
<>
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.jdbcPluginDesc}</p>
<table className="w-full table-auto border-collapse text-sm max-[760px]:block">
<thead className="bg-landing-panel text-xs font-medium text-landing-muted max-[760px]:hidden">
<tr className="border-b border-landing-line">
<th className="px-5 py-2.5 text-left font-medium">{t.jdbcPluginFile}</th>
<th className="px-5 py-2.5 text-left font-medium">{t.filename}</th>
<th className="px-5 py-2.5 text-left font-medium">{t.installMethod}</th>
<th className="w-24 px-5 py-2.5" />
</tr>
</thead>
<tbody className="max-[760px]:block">
{filteredJdbcPlugin.map((plugin) => (
<tr key={plugin.filename} className="border-b border-landing-line transition-colors last:border-b-0 hover:bg-landing-panel max-[760px]:grid max-[760px]:grid-cols-[1fr_auto] max-[760px]:items-center max-[760px]:gap-3 max-[760px]:px-4">
<td className="min-w-0 px-5 py-3 font-medium text-landing-ink max-[760px]:px-0">
<div className="flex min-w-0 items-center gap-2">
<span className="min-w-0 truncate">{plugin.label}</span>
<span className="hidden shrink-0 rounded-[5px] border border-landing-blue/35 bg-landing-blue/10 px-1.5 py-0.5 font-mono text-[11px] text-landing-sky max-[760px]:inline">ZIP</span>
</div>
<p className="mt-1 hidden text-xs leading-[1.55] text-landing-muted max-[760px]:block">{t.jdbcPluginInstallHint}</p>
</td>
<td className="px-5 py-3 font-mono text-[11px] text-landing-muted max-[760px]:hidden">{plugin.filename}</td>
<td className="px-5 py-3 text-xs text-landing-muted max-[760px]:hidden">{t.jdbcPluginInstallHint}</td>
<td className="px-5 py-3 text-right max-[760px]:px-0">
<a href={plugin.url} download className="landing-nav-link inline-flex h-8 items-center gap-1 whitespace-nowrap rounded-[6px] border border-landing-line px-2.5 text-xs font-medium transition-colors hover:border-landing-blue">
<Download size={13} />
{t.download}
</a>
</td>
</tr>
))}
</tbody>
</table>
{filteredJdbcPlugin.length === 0 && <div className="px-5 py-12 text-center text-sm text-landing-muted">{t.noResults}</div>}
</>
)}
{activeTab === "bundles" && (
<>
<p className="border-b border-landing-line px-5 py-3 text-sm text-landing-muted whitespace-nowrap max-[760px]:whitespace-normal max-[760px]:px-4">{t.bundlesDesc}</p>

View File

@ -0,0 +1,13 @@
import assert from "node:assert/strict";
import { test } from "vitest";
import { buildAgentDownloadCatalog } from "./agentRegistry";
test("offline download catalog includes the JDBC plugin ZIP", () => {
const catalog = buildAgentDownloadCatalog([]);
assert.deepEqual(catalog.jdbcPlugin, {
label: "DBX JDBC Plugin",
filename: "dbx-jdbc-plugin-latest.zip",
url: "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip",
});
});

View File

@ -50,7 +50,14 @@ export interface NativeAgentDisplayEntry {
info: ArtifactInfo;
}
export interface JdbcPluginDownloadEntry {
label: string;
filename: string;
url: string;
}
export interface AgentDownloadCatalog {
jdbcPlugin: JdbcPluginDownloadEntry;
bundles: OfflineBundleEntry[];
drivers: DriverDisplayEntry[];
jres: JreDisplayEntry[];
@ -58,6 +65,7 @@ export interface AgentDownloadCatalog {
}
const AGENTS_LATEST_RELEASE_API_URL = "https://api.github.com/repos/t8y2/dbx/releases/tags/agents-latest";
const JDBC_PLUGIN_DOWNLOAD_URL = "https://dl.dbxio.com/releases/latest/dbx-jdbc-plugin-latest.zip";
const MIN_APP_VERSION = "0.6.0";
const driverVersionMap = driverVersions as Record<string, string>;
const nativeDriverKeys = new Set(["oracle", "xugu"]);
@ -149,6 +157,7 @@ export async function fetchAgentDownloadCatalog(): Promise<AgentDownloadCatalog
export function buildAgentDownloadCatalog(assets: GitHubReleaseAsset[]): AgentDownloadCatalog {
return {
jdbcPlugin: buildJdbcPluginDownloadEntry(),
bundles: buildOfflineBundleEntries(assets),
drivers: buildDriverEntries(assets),
jres: buildJreEntries(assets),
@ -156,6 +165,14 @@ export function buildAgentDownloadCatalog(assets: GitHubReleaseAsset[]): AgentDo
};
}
export function buildJdbcPluginDownloadEntry(): JdbcPluginDownloadEntry {
return {
label: "DBX JDBC Plugin",
filename: "dbx-jdbc-plugin-latest.zip",
url: JDBC_PLUGIN_DOWNLOAD_URL,
};
}
export function buildJreEntries(assets: GitHubReleaseAsset[]): JreDisplayEntry[] {
return assets
.map((asset) => {