feat(agents): add versioned offline driver packages

This commit is contained in:
t8y2 committed 2026-07-21 13:42:39 +08:00
1 parent 8d68a527a0
commit 79cd12d13a
29 files changed
+1372 -162

No files matched your search

@@ -21,6 +21,7 @@ import { formatRuntimeBytes, formatRuntimeCpu, formatRuntimeUptime, runtimeHealt
import { addDriverInstallQueue, driverInstallProgressPercent, isDriverInstallProgressTarget, removeDriverInstallQueue, takeNextDriverInstallQueue, type DriverInstallProgress } from "@/lib/connection/driverInstallProgressUi";
import { PRESTOSQL_DRIVER_DB_TYPE, prestoSqlBuiltinDriverRow, prestoSqlMavenBundle } from "@/lib/database/prestoSqlBuiltinDriver";
import type { DriverStoreFocus } from "@/lib/connection/agentDriverInstallHint";
import { isOfflineDriverPackage, webDriverImportAccept } from "@/lib/driverStore/driverImportSelection";
const { t } = useI18n();
const { toast } = useToast();
@@ -550,19 +551,38 @@ async function importOfflineZip() {
}
}
async function importDriverJar(dbType: string) {
async function importDriverFile(driver: AgentDriverInfo) {
const dbType = driver.db_type;
if (isPrestoSqlBuiltinDriver(dbType)) {
await importJdbcDrivers();
return;
}
const blockers = await api.checkAgentUpdateBlockers([dbType]);
if (blockers.length > 0) {
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
return;
}
const label = driverLabel(dbType);
if (isWeb) {
const file = await chooseWebFile(".jar");
if (!file) return;
try {
await api.importAgentJar(dbType, file);
const requiresJavaRuntime = driverRequiresJavaRuntime(driver);
const isWindows = navigator.userAgent.toLowerCase().includes("windows");
const installSelectedFile = async (selected: string | File) => {
if (isOfflineDriverPackage(selected)) {
const count = await api.importAgentsFromZip(selected);
await refreshAgents();
toast(t("driverStore.offlineImportSuccess", { count }));
} else {
await api.importAgentDriver(dbType, selected);
await refreshAgents();
toast(t("driverStore.driverImportSuccess", { label }));
}
};
if (isWeb) {
// Native release assets have no extension on macOS/Linux, so do not apply
// a browser accept filter that would make the correct file unselectable.
const file = await chooseWebFile(webDriverImportAccept(requiresJavaRuntime, isWindows));
if (!file) return;
try {
await installSelectedFile(file);
} catch (e: any) {
toast(t("driverStore.driverImportFailed", { label, error: e }));
}
@@ -572,13 +592,11 @@ async function importDriverJar(dbType: string) {
const selected = await open({
title: t("driverStore.chooseDriverJar"),
multiple: false,
filters: [{ name: "JAR", extensions: ["jar"] }],
filters: requiresJavaRuntime ? [{ name: "Driver package or JAR", extensions: ["zip", "jar"] }] : isWindows ? [{ name: "Driver package or executable", extensions: ["zip", "exe"] }] : undefined,
});
if (typeof selected !== "string") return;
try {
await api.importAgentJar(dbType, selected);
await refreshAgents();
toast(t("driverStore.driverImportSuccess", { label }));
await installSelectedFile(selected);
} catch (e: any) {
toast(t("driverStore.driverImportFailed", { label, error: e }));
}
@@ -1293,7 +1311,7 @@ watch(driverStoreTab, (tab) => {
class="h-7 w-7 rounded-[6px] text-xs text-muted-foreground"
:title="t('driverStore.importLocalJar')"
:disabled="upgradingAll || installing !== null"
@click="importDriverJar(driver.db_type)"
@click="importDriverFile(driver)"
>
<FileUp class="h-3.5 w-3.5" />
</Button>
@@ -1359,7 +1377,7 @@ watch(driverStoreTab, (tab) => {
class="h-7 w-7 rounded-[6px] text-xs text-muted-foreground"
:title="t('driverStore.importLocalJar')"
:disabled="upgradingAll || installing !== null"
@click="importDriverJar(driver.db_type)"
@click="importDriverFile(driver)"
>
<FileUp class="h-3.5 w-3.5" />
</Button>
+2 -2
View File
@@ -4075,7 +4075,7 @@ export default {
offlineImportFailed: "Offline import failed: {error}",
driverImportSuccess: "{label} driver imported",
driverImportFailed: "Failed to import {label} driver: {error}",
chooseDriverJar: "Choose driver JAR file",
chooseDriverJar: "Choose driver file",
jreReinstallSuccess: "JRE {jre} reinstalled",
jreReinstallFailed: "Failed to reinstall JRE {jre}: {error}",
jreUninstallSuccess: "JRE {jre} uninstalled",
@@ -4109,7 +4109,7 @@ export default {
upgradingProgress: "Upgrading ({current}/{total})",
upgradeAll: "Upgrade all",
queued: "Queued",
importLocalJar: "Import local JAR",
importLocalJar: "Import local driver file",
updating: "Updating",
update: "Update",
localInstall: "Local install",
+2 -2
View File
@@ -3841,7 +3841,7 @@ export default withEnglishFallback({
offlineImportFailed: "Error en la importación offline: {error}",
driverImportSuccess: "Driver {label} importado",
driverImportFailed: "Error al importar el driver {label}: {error}",
chooseDriverJar: "Seleccionar archivo JAR del driver",
chooseDriverJar: "Seleccionar archivo del driver",
jreReinstallSuccess: "JRE {jre} reinstalado",
jreReinstallFailed: "Error al reinstalar JRE {jre}: {error}",
jreUninstallSuccess: "JRE {jre} desinstalado",
@@ -3875,7 +3875,7 @@ export default withEnglishFallback({
upgradingProgress: "Actualizando ({current}/{total})",
upgradeAll: "Actualizar todo",
queued: "En cola",
importLocalJar: "Importar JAR local",
importLocalJar: "Importar archivo de driver local",
updating: "Actualizando",
update: "Actualizar",
localInstall: "Instalación local",
+2 -2
View File
@@ -3839,7 +3839,7 @@ export default withEnglishFallback({
offlineImportFailed: "Importazione offline non riuscita: {error}",
driverImportSuccess: "Driver {label} importato",
driverImportFailed: "Importazione del driver {label} non riuscita: {error}",
chooseDriverJar: "Scegli il file JAR del driver",
chooseDriverJar: "Scegli il file del driver",
jreReinstallSuccess: "JRE {jre} reinstallato",
jreReinstallFailed: "Reinstallazione del JRE {jre} non riuscita: {error}",
jreUninstallSuccess: "JRE {jre} disinstallato",
@@ -3873,7 +3873,7 @@ export default withEnglishFallback({
upgradingProgress: "Aggiornamento in corso ({current}/{total})",
upgradeAll: "Aggiorna tutti",
queued: "In coda",
importLocalJar: "Importa JAR locale",
importLocalJar: "Importa file driver locale",
updating: "Aggiornamento in corso",
update: "Aggiorna",
localInstall: "Installazione locale",
+2 -2
View File
@@ -3839,7 +3839,7 @@ export default withEnglishFallback({
offlineImportFailed: "オフラインインポートに失敗しました: {error}",
driverImportSuccess: "{label} ドライバーをインポートしました",
driverImportFailed: "{label} ドライバーのインポートに失敗しました: {error}",
chooseDriverJar: "ドライバーJARファイルを選択",
chooseDriverJar: "ドライバーファイルを選択",
jreReinstallSuccess: "JRE {jre} を再インストールしました",
jreReinstallFailed: "JRE {jre} の再インストールに失敗しました: {error}",
jreUninstallSuccess: "JRE {jre} をアンインストールしました",
@@ -3873,7 +3873,7 @@ export default withEnglishFallback({
upgradingProgress: "アップグレード中 ({current}/{total})",
upgradeAll: "すべてアップグレード",
queued: "待機中",
importLocalJar: "ローカルJARをインポート",
importLocalJar: "ローカルドライバーファイルをインポート",
updating: "更新中",
update: "更新",
localInstall: "ローカルインストール",
+2 -2
View File
@@ -3841,7 +3841,7 @@ export default withEnglishFallback({
offlineImportFailed: "Falha na importação offline: {error}",
driverImportSuccess: "Driver {label} importado",
driverImportFailed: "Falha ao importar o driver {label}: {error}",
chooseDriverJar: "Escolher arquivo JAR do driver",
chooseDriverJar: "Escolher arquivo do driver",
jreReinstallSuccess: "JRE {jre} reinstalada",
jreReinstallFailed: "Falha ao reinstalar a JRE {jre}: {error}",
jreUninstallSuccess: "JRE {jre} desinstalada",
@@ -3875,7 +3875,7 @@ export default withEnglishFallback({
upgradingProgress: "Atualizando ({current}/{total})",
upgradeAll: "Atualizar todos",
queued: "Na fila",
importLocalJar: "Importar JAR local",
importLocalJar: "Importar arquivo de driver local",
updating: "Atualizando",
update: "Atualizar",
localInstall: "Instalação local",
+2 -2
View File
@@ -4065,7 +4065,7 @@ export default withEnglishFallback({
offlineImportFailed: "离线导入失败: {error}",
driverImportSuccess: "{label} 驱动导入成功",
driverImportFailed: "{label} 驱动导入失败: {error}",
chooseDriverJar: "选择驱动 JAR 文件",
chooseDriverJar: "选择驱动文件",
jreReinstallSuccess: "JRE {jre} 重新安装成功",
jreReinstallFailed: "JRE {jre} 重新安装失败: {error}",
jreUninstallSuccess: "JRE {jre} 已卸载",
@@ -4099,7 +4099,7 @@ export default withEnglishFallback({
upgradingProgress: "升级中 ({current}/{total})",
upgradeAll: "全部升级",
queued: "排队中",
importLocalJar: "导入本地 JAR",
importLocalJar: "导入本地驱动文件",
updating: "更新中",
update: "更新",
localInstall: "本地安装",
+2 -2
View File
@@ -3645,7 +3645,7 @@ export default withEnglishFallback({
offlineImportFailed: "離線匯入失敗: {error}",
driverImportSuccess: "{label} 驅動程式匯入成功",
driverImportFailed: "{label} 驅動程式匯入失敗: {error}",
chooseDriverJar: "選擇驅動程式 JAR 檔案",
chooseDriverJar: "選擇驅動程式檔案",
jreReinstallSuccess: "JRE {jre} 重新安裝成功",
jreReinstallFailed: "JRE {jre} 重新安裝失敗: {error}",
jreUninstallSuccess: "JRE {jre} 已解除安裝",
@@ -3679,7 +3679,7 @@ export default withEnglishFallback({
upgradingProgress: "更新中 ({current}/{total})",
upgradeAll: "全部更新",
queued: "排隊中",
importLocalJar: "匯入本機 JAR",
importLocalJar: "匯入本機驅動程式檔案",
updating: "更新中",
update: "更新",
localInstall: "本機安裝",
+2 -1
View File
@@ -109,7 +109,8 @@ export const getAgentJavaRuntimeConfig = forward("getAgentJavaRuntimeConfig");
export const setAgentJavaRuntimeConfig = forward("setAgentJavaRuntimeConfig");
export const invalidateAgentRegistryCache = forward("invalidateAgentRegistryCache");
export const importAgentsFromZip = forward("importAgentsFromZip");
export const importAgentJar = forward("importAgentJar");
export const importAgentDriver = forward("importAgentDriver");
export const importAgentJar = importAgentDriver;
export async function reinstallJre(jreKey?: string) {
const backend = await getBackend();
return backend.reinstallJre(jreKey, useSettingsStore().editorSettings.updateDownloadSource);
+5 -3
View File
@@ -457,23 +457,25 @@ export async function importAgentsFromZip(fileOrPath: string | File): Promise<nu
return result.count;
}
export async function importAgentJar(dbType: string, pathOrFile: string | File): Promise<void> {
export async function importAgentDriver(dbType: string, pathOrFile: string | File): Promise<void> {
let blob: Blob;
let fileName: string;
if (pathOrFile instanceof File) {
blob = pathOrFile;
fileName = pathOrFile.name;
} else {
fileName = pathOrFile.split("/").pop() || "driver.jar";
fileName = pathOrFile.split("/").pop() || "agent";
blob = await (await fetch(pathOrFile)).blob();
}
const formData = new FormData();
formData.append("dbType", dbType);
formData.append("file", blob, fileName);
const uploadRes = await fetch(apiUrl("/api/agents/import-jar"), { method: "POST", body: formData });
const uploadRes = await fetch(apiUrl("/api/agents/import-driver"), { method: "POST", body: formData });
if (!uploadRes.ok) throw new Error(await uploadRes.text());
}
export const importAgentJar = importAgentDriver;
export async function reinstallJre(jreKey?: string, _source?: UpdateDownloadSource): Promise<void> {
await post("/api/agents/reinstall-jre", { jreKey });
}
+5 -3
View File
@@ -1341,13 +1341,15 @@ export async function importAgentsFromZip(path: string | File): Promise<number>
return invoke("import_agents_from_zip", { path });
}
export async function importAgentJar(dbType: string, path: string | File): Promise<void> {
export async function importAgentDriver(dbType: string, path: string | File): Promise<void> {
if (typeof path !== "string") {
throw new Error("Desktop driver JAR import requires a local file path");
throw new Error("Desktop driver import requires a local file path");
}
return invoke("import_agent_jar_cmd", { dbType, path });
return invoke("import_agent_driver_cmd", { dbType, path });
}
export const importAgentJar = importAgentDriver;
export async function reinstallJre(jreKey?: string, source?: UpdateDownloadSource): Promise<void> {
return invoke("reinstall_jre", { jreKey, source });
}
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";
import { isOfflineDriverPackage, webDriverImportAccept } from "./driverImportSelection";
describe("driver import selection", () => {
it("recognizes ZIP paths and uploaded files case-insensitively", () => {
expect(isOfflineDriverPackage("C:\\Downloads\\dbx-agent-h2-0.2.5.ZIP")).toBe(true);
expect(isOfflineDriverPackage({ name: "dbx-agent-kingbase-0.1.34-macos-aarch64.zip" })).toBe(true);
expect(isOfflineDriverPackage({ name: "dbx-agent-h2-0.2.5.jar" })).toBe(false);
});
it("allows ZIP alongside the platform raw artifact", () => {
expect(webDriverImportAccept(true, false)).toBe(".zip,.jar");
expect(webDriverImportAccept(false, true)).toBe(".zip,.exe");
expect(webDriverImportAccept(false, false)).toBe("");
});
});
@@ -0,0 +1,11 @@
export type DriverImportSelection = string | { name: string };
export function isOfflineDriverPackage(selection: DriverImportSelection): boolean {
const name = typeof selection === "string" ? selection : selection.name;
return name.toLowerCase().endsWith(".zip");
}
export function webDriverImportAccept(requiresJavaRuntime: boolean, isWindows: boolean): string {
if (requiresJavaRuntime) return ".zip,.jar";
return isWindows ? ".zip,.exe" : "";
}