mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-02 08:30:35 +00:00
feat: enable driver management on web version
- Extract JDBC driver/plugin logic from Tauri commands to dbx-core::jdbc - Add web API routes for JDBC drivers, JDBC plugin, Agent JAR import, system fonts - Upgrade zip crate from v2 to v4 in dbx-core - Replace http.ts stubs with real HTTP calls (support File and string paths) - Remove isWeb restrictions in DriverStoreDialog, add browser file picker support - Fix Mac traffic light inset incorrectly applied on web version - Show driver management toolbar button on web version
This commit is contained in:
@@ -273,6 +273,34 @@ function chooseWebOfflineZip(): Promise<File | null> {
|
||||
});
|
||||
}
|
||||
|
||||
function chooseWebFiles(accept: string, multiple: boolean): Promise<File[] | null> {
|
||||
return new Promise((resolve) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = accept;
|
||||
input.multiple = multiple;
|
||||
input.onchange = () => {
|
||||
const files = input.files;
|
||||
if (!files || files.length === 0) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
resolve(Array.from(files));
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
|
||||
function chooseWebFile(accept: string): Promise<File | null> {
|
||||
return new Promise((resolve) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = accept;
|
||||
input.onchange = () => resolve(input.files?.[0] ?? null);
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
|
||||
async function importOfflineZip() {
|
||||
if (importingZip.value) return;
|
||||
let selected: string | File | null = null;
|
||||
@@ -303,7 +331,19 @@ async function importOfflineZip() {
|
||||
}
|
||||
|
||||
async function importDriverJar(dbType: string) {
|
||||
if (isWeb) return;
|
||||
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
|
||||
if (isWeb) {
|
||||
const file = await chooseWebFile(".jar");
|
||||
if (!file) return;
|
||||
try {
|
||||
await api.importAgentJar(dbType, file);
|
||||
await refreshAgents();
|
||||
toast(`${label} 驱动导入成功`);
|
||||
} catch (e: any) {
|
||||
toast(`${label} 驱动导入失败: ${e}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: "选择驱动 JAR 文件",
|
||||
@@ -311,7 +351,6 @@ async function importDriverJar(dbType: string) {
|
||||
filters: [{ name: "JAR", extensions: ["jar"] }],
|
||||
});
|
||||
if (typeof selected !== "string") return;
|
||||
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
|
||||
try {
|
||||
await api.importAgentJar(dbType, selected);
|
||||
await refreshAgents();
|
||||
@@ -373,7 +412,6 @@ function jreUsageLabel(key: string) {
|
||||
}
|
||||
|
||||
async function loadJdbcDrivers() {
|
||||
if (isWeb) return;
|
||||
isLoadingJdbcDrivers.value = true;
|
||||
try {
|
||||
jdbcDrivers.value = await api.listJdbcDrivers();
|
||||
@@ -394,7 +432,6 @@ async function loadDriverStoreUsage() {
|
||||
}
|
||||
|
||||
async function loadJdbcPluginStatus() {
|
||||
if (isWeb) return;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.jdbcPluginStatus();
|
||||
emitDriverUpdateCount();
|
||||
@@ -404,7 +441,7 @@ async function loadJdbcPluginStatus() {
|
||||
}
|
||||
|
||||
async function installJdbcPlugin() {
|
||||
if (isWeb || isInstallingJdbcPlugin.value) return;
|
||||
if (isInstallingJdbcPlugin.value) return;
|
||||
isInstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.installJdbcPlugin();
|
||||
@@ -419,14 +456,20 @@ async function installJdbcPlugin() {
|
||||
}
|
||||
|
||||
async function installJdbcPluginLocal() {
|
||||
if (isWeb || isInstallingJdbcPlugin.value) return;
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: "选择 JDBC 插件 zip 文件",
|
||||
multiple: false,
|
||||
filters: [{ name: "ZIP", extensions: ["zip"] }],
|
||||
});
|
||||
if (typeof selected !== "string") return;
|
||||
if (isInstallingJdbcPlugin.value) return;
|
||||
let selected: string | File | null = null;
|
||||
if (isWeb) {
|
||||
selected = await chooseWebFile(".zip");
|
||||
} else {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const result = await open({
|
||||
title: "选择 JDBC 插件 zip 文件",
|
||||
multiple: false,
|
||||
filters: [{ name: "ZIP", extensions: ["zip"] }],
|
||||
});
|
||||
selected = typeof result === "string" ? result : null;
|
||||
}
|
||||
if (!selected) return;
|
||||
isInstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.installJdbcPluginLocal(selected);
|
||||
@@ -441,7 +484,7 @@ async function installJdbcPluginLocal() {
|
||||
}
|
||||
|
||||
async function uninstallJdbcPlugin() {
|
||||
if (isWeb || isUninstallingJdbcPlugin.value) return;
|
||||
if (isUninstallingJdbcPlugin.value) return;
|
||||
isUninstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.uninstallJdbcPlugin();
|
||||
@@ -468,7 +511,18 @@ async function importJdbcDriverPaths(paths: string[]) {
|
||||
}
|
||||
|
||||
async function importJdbcDrivers() {
|
||||
if (isWeb) return;
|
||||
if (isWeb) {
|
||||
const files = await chooseWebFiles(".jar", true);
|
||||
if (!files || !files.length) return;
|
||||
try {
|
||||
jdbcDrivers.value = await api.importJdbcDrivers(files);
|
||||
void loadDriverStoreUsage();
|
||||
toast(t("settings.jdbcImportSuccess", { count: files.length }));
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: t("settings.jdbcImport"),
|
||||
|
||||
@@ -82,7 +82,7 @@ function onToolbarDblClick(e: MouseEvent) {
|
||||
<template>
|
||||
<div
|
||||
class="h-10 flex items-center gap-1 px-2 border-b bg-muted/30 shrink-0"
|
||||
:class="{ 'pl-17.5': shouldReserveMacTrafficLightInset(isMac, isFullscreen) }"
|
||||
:class="{ 'pl-17.5': shouldReserveMacTrafficLightInset(isMac, isFullscreen, isDesktop) }"
|
||||
data-tauri-drag-region
|
||||
@dblclick="onToolbarDblClick"
|
||||
>
|
||||
@@ -147,7 +147,6 @@ function onToolbarDblClick(e: MouseEvent) {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-if="isDesktop"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8 px-2 text-xs gap-1"
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { isMacOS } from "@/lib/platform";
|
||||
|
||||
export function shouldReserveMacTrafficLightInset(isMac: boolean, isFullscreen: boolean): boolean {
|
||||
return isMac && !isFullscreen;
|
||||
export function shouldReserveMacTrafficLightInset(isMac: boolean, isFullscreen: boolean, isDesktop: boolean): boolean {
|
||||
return isDesktop && isMac && !isFullscreen;
|
||||
}
|
||||
|
||||
export function useWindowControls() {
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function loadConnections(): Promise<ConnectionConfig[]> {
|
||||
}
|
||||
|
||||
export async function listSystemFonts(): Promise<string[]> {
|
||||
return [];
|
||||
return get("/api/system/fonts");
|
||||
}
|
||||
|
||||
export async function listPlugins(): Promise<InstalledPlugin[]> {
|
||||
@@ -159,40 +159,57 @@ export async function listPlugins(): Promise<InstalledPlugin[]> {
|
||||
}
|
||||
|
||||
export async function listJdbcDrivers(): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
return get("/api/jdbc/drivers");
|
||||
}
|
||||
|
||||
export async function importJdbcDrivers(_paths: string[]): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
export async function importJdbcDrivers(pathsOrFiles: (string | File)[]): Promise<JdbcDriverInfo[]> {
|
||||
const formData = new FormData();
|
||||
for (const item of pathsOrFiles) {
|
||||
if (item instanceof File) {
|
||||
formData.append("files", item, item.name);
|
||||
} else {
|
||||
const fileName = item.split("/").pop() || "driver.jar";
|
||||
const blob = await (await fetch(item)).blob();
|
||||
formData.append("files", blob, fileName);
|
||||
}
|
||||
}
|
||||
const res = await fetch("/api/jdbc/drivers", { method: "POST", body: formData });
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function deleteJdbcDriver(_path: string): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
export async function deleteJdbcDriver(path: string): Promise<JdbcDriverInfo[]> {
|
||||
const fileName = path.split("/").pop() || path;
|
||||
return del(`/api/jdbc/drivers/${encodeURIComponent(fileName)}`);
|
||||
}
|
||||
|
||||
export async function jdbcPluginStatus(): Promise<JdbcPluginStatus> {
|
||||
return {
|
||||
installed: false,
|
||||
version: null,
|
||||
protocol_version: null,
|
||||
compatible: true,
|
||||
latest_version: null,
|
||||
latest_protocol_version: null,
|
||||
update_available: false,
|
||||
path: "",
|
||||
};
|
||||
return get("/api/jdbc/plugin/status");
|
||||
}
|
||||
|
||||
export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return jdbcPluginStatus();
|
||||
return post("/api/jdbc/plugin/install", {});
|
||||
}
|
||||
|
||||
export async function installJdbcPluginLocal(_path: string): Promise<JdbcPluginStatus> {
|
||||
return jdbcPluginStatus();
|
||||
export async function installJdbcPluginLocal(pathOrFile: string | File): Promise<JdbcPluginStatus> {
|
||||
let blob: Blob;
|
||||
let fileName: string;
|
||||
if (pathOrFile instanceof File) {
|
||||
blob = pathOrFile;
|
||||
fileName = pathOrFile.name;
|
||||
} else {
|
||||
fileName = pathOrFile.split("/").pop() || "plugin.zip";
|
||||
blob = await (await fetch(pathOrFile)).blob();
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append("file", blob, fileName);
|
||||
const uploadRes = await fetch("/api/jdbc/plugin/install-local", { method: "POST", body: formData });
|
||||
if (!uploadRes.ok) throw new Error(await uploadRes.text());
|
||||
return uploadRes.json();
|
||||
}
|
||||
|
||||
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return jdbcPluginStatus();
|
||||
return post("/api/jdbc/plugin/uninstall", {});
|
||||
}
|
||||
|
||||
export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
||||
@@ -244,8 +261,21 @@ export async function importAgentsFromZip(fileOrPath: string | File): Promise<nu
|
||||
return result.count;
|
||||
}
|
||||
|
||||
export async function importAgentJar(_dbType: string, _path: string): Promise<void> {
|
||||
throw new Error("Local JAR import is only available in the desktop app");
|
||||
export async function importAgentJar(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";
|
||||
blob = await (await fetch(pathOrFile)).blob();
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append("dbType", dbType);
|
||||
formData.append("file", blob, fileName);
|
||||
const uploadRes = await fetch("/api/agents/import-jar", { method: "POST", body: formData });
|
||||
if (!uploadRes.ok) throw new Error(await uploadRes.text());
|
||||
}
|
||||
|
||||
export async function reinstallJre(jreKey?: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user