mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-31 08:23:34 +00:00
feat(jdbc): add optional JDBC plugin support
This commit is contained in:
@@ -6,16 +6,17 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
import type { ConnectionConfig, DatabaseType, JdbcDriverInfo } from "@/types/database";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import * as api from "@/lib/api";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { applyParsedConnectionUrl, parseConnectionUrl } from "@/lib/connectionUrl";
|
||||
import { ArrowLeft, ChevronRight, Copy, FolderOpen, Grid3X3, Link2, List, Search } from "lucide-vue-next";
|
||||
import { ArrowLeft, ChevronRight, Copy, ExternalLink, FolderOpen, Grid3X3, Link2, List, Search } from "lucide-vue-next";
|
||||
|
||||
type DbOption = { value: string; label: string };
|
||||
type DbCategory = { key: string; title: string; options: DbOption[] };
|
||||
@@ -68,12 +69,17 @@ const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
|
||||
ssh_connect_timeout_secs: 5,
|
||||
ssl: false,
|
||||
connection_string: undefined,
|
||||
jdbc_driver_class: undefined,
|
||||
jdbc_driver_paths: [],
|
||||
});
|
||||
|
||||
const form = ref(defaultForm());
|
||||
const selectedType = ref("mysql");
|
||||
const customDriverName = ref("");
|
||||
const mongoUseUrl = ref(false);
|
||||
const jdbcDriverPathsInput = ref("");
|
||||
const jdbcDrivers = ref<JdbcDriverInfo[]>([]);
|
||||
const selectedJdbcDriverPath = ref("");
|
||||
const connectionUrlInput = ref("");
|
||||
const dialogStep = ref<DialogStep>("select");
|
||||
const dbPickerView = ref<DbPickerView>("icon");
|
||||
@@ -170,6 +176,7 @@ const driverProfiles: Record<
|
||||
icon: "cockroachdb",
|
||||
},
|
||||
dm: { type: "dameng", port: 5236, user: "SYSDBA", label: "DM (Dameng)", icon: "dm" },
|
||||
jdbc: { type: "jdbc", port: 0, user: "", label: "JDBC", icon: "jdbc" },
|
||||
tdengine: { type: "mysql", port: 6030, user: "root", label: "TDengine", icon: "tdengine" },
|
||||
custom_mysql: {
|
||||
type: "mysql",
|
||||
@@ -220,6 +227,13 @@ function applyProfile(val: string, preserveConnectionFields = false) {
|
||||
if (profile.type === "sqlite" || profile.type === "duckdb") {
|
||||
form.value.host = "";
|
||||
}
|
||||
if (profile.type === "jdbc") {
|
||||
form.value.host = "";
|
||||
form.value.connection_string = "";
|
||||
form.value.jdbc_driver_class = "";
|
||||
form.value.jdbc_driver_paths = [];
|
||||
jdbcDriverPathsInput.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,9 +266,12 @@ watch(
|
||||
ssh_connect_timeout_secs: config.ssh_connect_timeout_secs || 5,
|
||||
ssl: config.ssl || false,
|
||||
connection_string: config.connection_string,
|
||||
jdbc_driver_class: config.jdbc_driver_class,
|
||||
jdbc_driver_paths: config.jdbc_driver_paths || [],
|
||||
};
|
||||
selectedType.value = profile;
|
||||
mongoUseUrl.value = !!config.connection_string;
|
||||
jdbcDriverPathsInput.value = (config.jdbc_driver_paths || []).join("\n");
|
||||
customDriverName.value = isCustomCompatibleProfile() ? config.driver_label || "" : "";
|
||||
dialogStep.value = "config";
|
||||
configTab.value = "connection";
|
||||
@@ -330,6 +347,7 @@ const iconTypeMap: Record<string, string> = {
|
||||
cockroachdb: "cockroachdb",
|
||||
tdengine: "tdengine",
|
||||
dm: "dm",
|
||||
jdbc: "jdbc",
|
||||
custom_mysql: "mysql",
|
||||
custom_postgres: "postgres",
|
||||
};
|
||||
@@ -360,6 +378,7 @@ const dbOptions = [
|
||||
{ value: "vastbase", label: "Vastbase" },
|
||||
{ value: "redshift", label: "Redshift" },
|
||||
{ value: "cockroachdb", label: "CockroachDB" },
|
||||
{ value: "jdbc", label: "JDBC" },
|
||||
{ value: "custom_mysql", label: "Custom (MySQL)" },
|
||||
{ value: "custom_postgres", label: "Custom (PostgreSQL)" },
|
||||
];
|
||||
@@ -387,7 +406,8 @@ const filteredDbCategories = computed<DbCategory[]>(() => {
|
||||
|
||||
const hasDbPickerResults = computed(() => filteredDbCategories.value.some((category) => category.options.length > 0));
|
||||
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
|
||||
const canUseSsh = computed(() => form.value.db_type !== "sqlite");
|
||||
const isJdbcConnection = computed(() => form.value.db_type === "jdbc");
|
||||
const canUseSsh = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "jdbc");
|
||||
const testResultMessage = computed(() => {
|
||||
if (!testResult.value) return "";
|
||||
return testResult.value.ok ? t("connection.testSuccess") : testResult.value.message;
|
||||
@@ -439,6 +459,16 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
if (config.db_type === "mongodb" && !mongoUseUrl.value) {
|
||||
config.connection_string = undefined;
|
||||
}
|
||||
if (config.db_type === "jdbc") {
|
||||
config.host = "";
|
||||
config.port = 0;
|
||||
config.connection_string = config.connection_string?.trim() || "";
|
||||
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || undefined;
|
||||
config.jdbc_driver_paths = jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((path) => path.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -477,6 +507,8 @@ function resetForm() {
|
||||
selectedType.value = "mysql";
|
||||
customDriverName.value = "";
|
||||
mongoUseUrl.value = false;
|
||||
jdbcDriverPathsInput.value = "";
|
||||
selectedJdbcDriverPath.value = "";
|
||||
connectionUrlInput.value = "";
|
||||
dialogStep.value = "select";
|
||||
dbPickerView.value = "icon";
|
||||
@@ -493,6 +525,7 @@ watch(open, (value) => {
|
||||
if (!props.editConfig) {
|
||||
resetForm();
|
||||
}
|
||||
void loadJdbcDrivers();
|
||||
});
|
||||
|
||||
watch(canUseSsh, (value) => {
|
||||
@@ -513,6 +546,10 @@ async function save() {
|
||||
} else {
|
||||
const config = connectionConfigForSubmit(uuid());
|
||||
await store.addConnection(config);
|
||||
if (config.db_type === "jdbc") {
|
||||
open.value = false;
|
||||
return;
|
||||
}
|
||||
open.value = false;
|
||||
await nextTick();
|
||||
emit("connectStarted", config.name);
|
||||
@@ -569,6 +606,58 @@ async function browseDbFilePath() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function browseJdbcDriverPaths() {
|
||||
if (!isTauriRuntime()) return;
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: t("connection.jdbcDriverBrowse"),
|
||||
multiple: true,
|
||||
filters: [{ name: "JDBC Driver", extensions: ["jar"] }],
|
||||
});
|
||||
if (!selected) return;
|
||||
|
||||
const paths = Array.isArray(selected) ? selected : [selected];
|
||||
const existing = jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((path) => path.trim())
|
||||
.filter(Boolean);
|
||||
const merged = Array.from(
|
||||
new Set([...existing, ...paths.filter((path): path is string => typeof path === "string")]),
|
||||
);
|
||||
jdbcDriverPathsInput.value = merged.join("\n");
|
||||
}
|
||||
|
||||
async function loadJdbcDrivers() {
|
||||
if (!isDesktop) return;
|
||||
try {
|
||||
jdbcDrivers.value = await api.listJdbcDrivers();
|
||||
} catch {
|
||||
jdbcDrivers.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function addJdbcDriverPath(path: string) {
|
||||
const existing = jdbcDriverPathsInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
jdbcDriverPathsInput.value = Array.from(new Set([...existing, path])).join("\n");
|
||||
}
|
||||
|
||||
function onJdbcDriverSelect(path: any) {
|
||||
if (typeof path !== "string" || !path) return;
|
||||
selectedJdbcDriverPath.value = path;
|
||||
addJdbcDriverPath(path);
|
||||
}
|
||||
|
||||
function openExternalUrl(url: string) {
|
||||
if (isTauriRuntime()) {
|
||||
import("@tauri-apps/plugin-shell").then(({ open }) => open(url));
|
||||
} else {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -689,17 +778,17 @@ async function browseDbFilePath() {
|
||||
<template v-else>
|
||||
<div class="space-y-3">
|
||||
<Tabs v-model="configTab" class="min-h-0">
|
||||
<div class="flex items-center justify-between border-b pb-2">
|
||||
<div v-if="canUseSsh" class="flex items-center justify-between border-b pb-2">
|
||||
<TabsList>
|
||||
<TabsTrigger value="connection">{{ t("connection.basicTab") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="canUseSsh" value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
|
||||
<TabsTrigger value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="connection" class="m-0">
|
||||
<div class="grid gap-4 py-4 pr-2 max-h-[65vh] overflow-y-auto">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">URL</Label>
|
||||
<div v-if="!isJdbcConnection" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.connectionUrlOptional") }}</Label>
|
||||
<div class="col-span-3 flex items-center gap-1">
|
||||
<Input
|
||||
v-model="connectionUrlInput"
|
||||
@@ -765,8 +854,90 @@ async function browseDbFilePath() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JDBC: optional external plugin -->
|
||||
<template v-if="form.db_type === 'jdbc'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.jdbcUrl") }}</Label>
|
||||
<Input
|
||||
v-model="form.connection_string"
|
||||
class="col-span-3"
|
||||
:placeholder="t('connection.jdbcUrlPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.user") }}</Label>
|
||||
<Input v-model="form.username" class="col-span-3" placeholder="sa" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<Input v-model="form.password" type="password" class="col-span-3" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.jdbcDriverClass") }}</Label>
|
||||
<Input
|
||||
v-model="form.jdbc_driver_class"
|
||||
class="col-span-3"
|
||||
:placeholder="t('connection.jdbcDriverClassPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label class="text-right mt-2">{{ t("connection.jdbcDriverPaths") }}</Label>
|
||||
<div class="col-span-3 space-y-2">
|
||||
<Select
|
||||
v-if="jdbcDrivers.length > 0"
|
||||
:model-value="selectedJdbcDriverPath"
|
||||
@update:model-value="onJdbcDriverSelect"
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('connection.jdbcDriverSelectPlaceholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="driver in jdbcDrivers" :key="driver.path" :value="driver.path">
|
||||
{{ driver.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="flex items-start gap-1">
|
||||
<textarea
|
||||
v-model="jdbcDriverPathsInput"
|
||||
class="flex min-h-12 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
:placeholder="t('connection.jdbcDriverPathsPlaceholder')"
|
||||
/>
|
||||
<Tooltip v-if="isDesktop">
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="h-9 w-9 shrink-0"
|
||||
@click="browseJdbcDriverPaths"
|
||||
>
|
||||
<FolderOpen class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t("connection.jdbcDriverBrowse") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<span />
|
||||
<div class="col-span-3 space-y-2">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("connection.jdbcPluginHint") }}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button type="button" variant="outline" size="sm" @click="openExternalUrl('https://dbxio.com')">
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
{{ t("connection.jdbcDocs") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- SQLite / DuckDB: file path only -->
|
||||
<template v-if="form.db_type === 'sqlite' || form.db_type === 'duckdb'">
|
||||
<template v-else-if="form.db_type === 'sqlite' || form.db_type === 'duckdb'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.filePath") }}</Label>
|
||||
<div class="col-span-3 flex items-center gap-1">
|
||||
@@ -1074,9 +1245,21 @@ async function browseDbFilePath() {
|
||||
<Button
|
||||
class="shrink-0"
|
||||
@click="save"
|
||||
:disabled="isSaving || !form.name || (!form.host && !(mongoUseUrl && form.connection_string))"
|
||||
:disabled="
|
||||
isSaving ||
|
||||
!form.name ||
|
||||
(!form.host &&
|
||||
!(mongoUseUrl && form.connection_string) &&
|
||||
!(form.db_type === 'jdbc' && form.connection_string))
|
||||
"
|
||||
>
|
||||
{{ isSaving ? t("common.loading") : editingId ? t("connection.save") : t("connection.saveAndConnect") }}
|
||||
{{
|
||||
isSaving
|
||||
? t("common.loading")
|
||||
: editingId || isJdbcConnection
|
||||
? t("connection.save")
|
||||
: t("connection.saveAndConnect")
|
||||
}}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, watch, shallowRef, computed } from "vue";
|
||||
import type { EditorView as EditorViewType } from "@codemirror/view";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Settings } from "lucide-vue-next";
|
||||
import { FolderOpen, Settings, Trash2 } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -13,9 +13,13 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useSettingsStore, EDITOR_THEMES, FONT_FAMILIES, DEFAULT_EDITOR_SETTINGS } from "@/stores/settingsStore";
|
||||
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import type { JdbcDriverInfo, JdbcPluginStatus } from "@/types/database";
|
||||
import * as api from "@/lib/api";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingsStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
@@ -98,6 +102,12 @@ function setAppLayout(value: "separated" | "classic") {
|
||||
|
||||
const activeSettingsTab = ref("editor");
|
||||
const isWeb = !isTauriRuntime();
|
||||
const jdbcDrivers = ref<JdbcDriverInfo[]>([]);
|
||||
const isLoadingJdbcDrivers = ref(false);
|
||||
const jdbcPluginStatus = ref<JdbcPluginStatus | null>(null);
|
||||
const isInstallingJdbcPlugin = ref(false);
|
||||
const isUninstallingJdbcPlugin = ref(false);
|
||||
const jdbcDriverPathInput = ref("");
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
@@ -108,6 +118,8 @@ watch(
|
||||
oldPassword.value = "";
|
||||
newPassword.value = "";
|
||||
confirmNewPassword.value = "";
|
||||
void loadJdbcDrivers();
|
||||
void loadJdbcPluginStatus();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -153,6 +165,105 @@ async function changePassword() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number) {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
async function loadJdbcDrivers() {
|
||||
if (isWeb) return;
|
||||
isLoadingJdbcDrivers.value = true;
|
||||
try {
|
||||
jdbcDrivers.value = await api.listJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
} finally {
|
||||
isLoadingJdbcDrivers.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadJdbcPluginStatus() {
|
||||
if (isWeb) return;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.jdbcPluginStatus();
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function installJdbcPlugin() {
|
||||
if (isWeb || isInstallingJdbcPlugin.value) return;
|
||||
isInstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.installJdbcPlugin();
|
||||
toast(t("settings.jdbcPluginInstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
} finally {
|
||||
isInstallingJdbcPlugin.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function uninstallJdbcPlugin() {
|
||||
if (isWeb || isUninstallingJdbcPlugin.value) return;
|
||||
isUninstallingJdbcPlugin.value = true;
|
||||
try {
|
||||
jdbcPluginStatus.value = await api.uninstallJdbcPlugin();
|
||||
toast(t("settings.jdbcPluginUninstallSuccess"));
|
||||
await loadJdbcDrivers();
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
} finally {
|
||||
isUninstallingJdbcPlugin.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function importJdbcDriverPaths(paths: string[]) {
|
||||
if (!paths.length) return;
|
||||
try {
|
||||
jdbcDrivers.value = await api.importJdbcDrivers(paths);
|
||||
jdbcDriverPathInput.value = "";
|
||||
toast(t("settings.jdbcImportSuccess", { count: paths.length }));
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async function importJdbcDrivers() {
|
||||
if (isWeb) return;
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const selected = await open({
|
||||
title: t("settings.jdbcImport"),
|
||||
multiple: true,
|
||||
filters: [{ name: "JDBC Driver", extensions: ["jar"] }],
|
||||
});
|
||||
if (!selected) return;
|
||||
|
||||
const paths = (Array.isArray(selected) ? selected : [selected]).filter(
|
||||
(path): path is string => typeof path === "string",
|
||||
);
|
||||
await importJdbcDriverPaths(paths);
|
||||
}
|
||||
|
||||
async function importJdbcDriverPathInput() {
|
||||
const paths = jdbcDriverPathInput.value
|
||||
.split(/\r?\n/)
|
||||
.map((path) => path.trim())
|
||||
.filter(Boolean);
|
||||
await importJdbcDriverPaths(paths);
|
||||
}
|
||||
|
||||
async function deleteJdbcDriver(path: string) {
|
||||
try {
|
||||
jdbcDrivers.value = await api.deleteJdbcDriver(path);
|
||||
toast(t("settings.jdbcDeleteSuccess"));
|
||||
} catch (e: any) {
|
||||
toast(String(e?.message || e), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- CodeMirror preview ----------
|
||||
const previewRef = ref<HTMLDivElement>();
|
||||
const previewView = shallowRef<EditorViewType | null>(null);
|
||||
@@ -261,6 +372,7 @@ watch(
|
||||
<TabsList class="w-full">
|
||||
<TabsTrigger value="editor" class="flex-1">{{ t("settings.editorTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="appearance" class="flex-1">{{ t("settings.appearanceTab") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="!isWeb" value="jdbc" class="flex-1">{{ t("settings.jdbcTab") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="isWeb" value="security" class="flex-1">{{ t("settings.securityTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="about" class="flex-1">{{ t("settings.aboutTab") }}</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -382,7 +494,7 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="border-t-0 bg-transparent gap-2 sm:gap-0">
|
||||
<DialogFooter class="border-t-0 bg-transparent gap-3 sm:gap-3">
|
||||
<Button variant="outline" @click="resetDefaults">
|
||||
{{ t("settings.resetDefaults") }}
|
||||
</Button>
|
||||
@@ -427,7 +539,7 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="border-t-0 bg-transparent gap-2 sm:gap-0">
|
||||
<DialogFooter class="border-t-0 bg-transparent gap-3 sm:gap-3">
|
||||
<Button variant="outline" @click="resetDefaults">
|
||||
{{ t("settings.resetDefaults") }}
|
||||
</Button>
|
||||
@@ -441,6 +553,100 @@ watch(
|
||||
</DialogFooter>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent v-if="!isWeb" value="jdbc" class="space-y-5 py-2">
|
||||
<div class="rounded-md border bg-muted/20 p-4">
|
||||
<div class="flex min-h-12 items-center justify-between gap-3">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<Label>{{ t("settings.jdbcPlugin") }}</Label>
|
||||
<p v-if="!jdbcPluginStatus?.installed" class="text-xs text-muted-foreground">
|
||||
{{ t("settings.jdbcPluginNotInstalled") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex shrink-0 items-center gap-3">
|
||||
<span
|
||||
v-if="jdbcPluginStatus?.installed"
|
||||
class="text-xs"
|
||||
:class="jdbcPluginStatus.compatible ? 'text-green-600' : 'text-destructive'"
|
||||
>
|
||||
{{
|
||||
jdbcPluginStatus.compatible
|
||||
? t("settings.jdbcPluginInstalled", {
|
||||
version: jdbcPluginStatus.version || "-",
|
||||
})
|
||||
: t("settings.jdbcPluginIncompatible")
|
||||
}}
|
||||
</span>
|
||||
<Button
|
||||
v-if="jdbcPluginStatus?.installed"
|
||||
type="button"
|
||||
variant="outline"
|
||||
:disabled="isUninstallingJdbcPlugin"
|
||||
@click="uninstallJdbcPlugin"
|
||||
>
|
||||
{{ isUninstallingJdbcPlugin ? t("common.loading") : t("settings.jdbcPluginUninstall") }}
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
type="button"
|
||||
variant="default"
|
||||
:disabled="isInstallingJdbcPlugin"
|
||||
@click="installJdbcPlugin"
|
||||
>
|
||||
{{ isInstallingJdbcPlugin ? t("common.loading") : t("settings.jdbcPluginInstall") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-1">
|
||||
<Label>{{ t("settings.jdbcDrivers") }}</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
v-model="jdbcDriverPathInput"
|
||||
class="flex-1"
|
||||
:placeholder="t('settings.jdbcDriverPathPlaceholder')"
|
||||
@keydown.enter.prevent="importJdbcDriverPathInput"
|
||||
/>
|
||||
<Button variant="outline" :disabled="!jdbcDriverPathInput.trim()" @click="importJdbcDriverPathInput">
|
||||
{{ t("settings.jdbcImportPath") }}
|
||||
</Button>
|
||||
<Button class="shrink-0" @click="importJdbcDrivers">
|
||||
<FolderOpen class="h-4 w-4" />
|
||||
{{ t("settings.jdbcImport") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<div v-if="isLoadingJdbcDrivers" class="p-4 text-sm text-muted-foreground">
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div v-else-if="jdbcDrivers.length === 0" class="p-4 text-sm text-muted-foreground">
|
||||
{{ t("settings.jdbcNoDrivers") }}
|
||||
</div>
|
||||
<div v-else class="divide-y">
|
||||
<div v-for="driver in jdbcDrivers" :key="driver.path" class="flex items-center gap-3 p-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ driver.name }}</div>
|
||||
<div class="truncate text-xs text-muted-foreground">{{ driver.path }}</div>
|
||||
</div>
|
||||
<div class="shrink-0 text-xs text-muted-foreground">{{ formatBytes(driver.size) }}</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 shrink-0" @click="deleteJdbcDriver(driver.path)">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="border-t-0 bg-transparent">
|
||||
<Button variant="outline" @click="emit('update:open', false)">
|
||||
{{ t("common.close") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent v-if="isWeb" value="security" class="space-y-5 py-2">
|
||||
<div class="space-y-3">
|
||||
<Label class="text-base">{{ t("auth.changePassword") }}</Label>
|
||||
|
||||
@@ -99,6 +99,7 @@ export default {
|
||||
driverName: "Driver Name",
|
||||
driverNamePlaceholder: "Vendor or environment name",
|
||||
urlParams: "URL Params",
|
||||
connectionUrlOptional: "URL (optional)",
|
||||
connectionUrlPlaceholder: "postgresql://user:pass{'@'}host:5432/db?sslmode=require",
|
||||
parseConnectionUrl: "Parse connection URL",
|
||||
parseConnectionUrlApplied: "Connection URL applied",
|
||||
@@ -139,6 +140,16 @@ export default {
|
||||
sshKeyPathBrowse: "Browse",
|
||||
sshExposeLan: "Expose tunnel to LAN",
|
||||
sshConnectTimeout: "SSH Timeout (seconds)",
|
||||
jdbcUrl: "JDBC URL",
|
||||
jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/database",
|
||||
jdbcDriverClass: "Driver Class (optional)",
|
||||
jdbcDriverClassPlaceholder: "Most drivers auto-register; use com.vendor.jdbc.Driver if needed",
|
||||
jdbcDriverPaths: "Driver JARs",
|
||||
jdbcDriverSelectPlaceholder: "Choose imported driver",
|
||||
jdbcDriverPathsPlaceholder: "/path/to/driver.jar\n/path/to/another-driver.jar",
|
||||
jdbcDriverBrowse: "Choose JDBC driver JAR",
|
||||
jdbcDocs: "View JDBC docs",
|
||||
jdbcPluginHint: "Install the DBX JDBC plugin first, then import the database vendor's JDBC driver JAR.",
|
||||
dmCompatHint: "Requires DM8 ODBC driver installed on your system.",
|
||||
dmDownload: "Download from Dameng",
|
||||
mongoLegacyHint:
|
||||
@@ -786,6 +797,7 @@ export default {
|
||||
title: "Settings",
|
||||
editorTab: "Editor",
|
||||
appearanceTab: "Appearance",
|
||||
jdbcTab: "JDBC Drivers",
|
||||
securityTab: "Security",
|
||||
aboutTab: "About",
|
||||
fontFamily: "Font Family",
|
||||
@@ -806,6 +818,21 @@ export default {
|
||||
wordWrap: "Word wrap",
|
||||
wordWrapDescription: "Wrap long SQL lines within the editor width",
|
||||
preview: "Live Preview",
|
||||
jdbcPlugin: "DBX JDBC plugin",
|
||||
jdbcPluginInstall: "Install JDBC plugin",
|
||||
jdbcPluginInstallSuccess: "JDBC plugin installed",
|
||||
jdbcPluginUninstall: "Uninstall",
|
||||
jdbcPluginUninstallSuccess: "JDBC plugin uninstalled",
|
||||
jdbcPluginInstalled: "Installed: v{version}",
|
||||
jdbcPluginIncompatible: "Incompatible protocol",
|
||||
jdbcPluginNotInstalled: "Not installed",
|
||||
jdbcDrivers: "JDBC driver JARs",
|
||||
jdbcImport: "Import JAR",
|
||||
jdbcImportPath: "Import Path",
|
||||
jdbcDriverPathPlaceholder: "/path/to/driver.jar",
|
||||
jdbcImportSuccess: "Imported {count} driver file(s)",
|
||||
jdbcDeleteSuccess: "Driver removed",
|
||||
jdbcNoDrivers: "No JDBC drivers imported yet.",
|
||||
aboutDescription: "An open-source, lightweight database management tool.",
|
||||
community: "Community",
|
||||
qqGroup: "QQ Group",
|
||||
|
||||
@@ -98,6 +98,7 @@ export default {
|
||||
driverName: "驱动名称",
|
||||
driverNamePlaceholder: "厂商或环境名称",
|
||||
urlParams: "URL 参数",
|
||||
connectionUrlOptional: "URL(可选)",
|
||||
connectionUrlPlaceholder: "postgresql://user:pass{'@'}host:5432/db?sslmode=require",
|
||||
parseConnectionUrl: "解析连接 URL",
|
||||
parseConnectionUrlApplied: "已应用连接 URL",
|
||||
@@ -138,6 +139,16 @@ export default {
|
||||
sshKeyPathBrowse: "浏览",
|
||||
sshExposeLan: "允许局域网访问隧道",
|
||||
sshConnectTimeout: "SSH 超时时间(秒)",
|
||||
jdbcUrl: "JDBC URL",
|
||||
jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/database",
|
||||
jdbcDriverClass: "驱动类(可选)",
|
||||
jdbcDriverClassPlaceholder: "多数驱动可自动识别;需要时填写 com.vendor.jdbc.Driver",
|
||||
jdbcDriverPaths: "驱动 JAR",
|
||||
jdbcDriverSelectPlaceholder: "选择已导入驱动",
|
||||
jdbcDriverPathsPlaceholder: "/path/to/driver.jar\n/path/to/another-driver.jar",
|
||||
jdbcDriverBrowse: "选择 JDBC 驱动 JAR",
|
||||
jdbcDocs: "查看 JDBC 文档",
|
||||
jdbcPluginHint: "先安装 DBX JDBC 插件,再导入数据库厂商提供的 JDBC 驱动 JAR。",
|
||||
dmCompatHint: "需要在系统上安装达梦 DM8 ODBC 驱动程序。",
|
||||
dmDownload: "前往达梦官网下载",
|
||||
mongoLegacyHint:
|
||||
@@ -773,6 +784,7 @@ export default {
|
||||
title: "设置",
|
||||
editorTab: "编辑器",
|
||||
appearanceTab: "外观",
|
||||
jdbcTab: "JDBC 驱动",
|
||||
securityTab: "安全",
|
||||
aboutTab: "关于我们",
|
||||
fontFamily: "字体",
|
||||
@@ -793,6 +805,21 @@ export default {
|
||||
wordWrap: "自动换行",
|
||||
wordWrapDescription: "长 SQL 在编辑器宽度内自动折行显示",
|
||||
preview: "实时预览",
|
||||
jdbcPlugin: "DBX JDBC 插件",
|
||||
jdbcPluginInstall: "安装 JDBC 插件",
|
||||
jdbcPluginInstallSuccess: "JDBC 插件已安装",
|
||||
jdbcPluginUninstall: "卸载",
|
||||
jdbcPluginUninstallSuccess: "JDBC 插件已卸载",
|
||||
jdbcPluginInstalled: "已安装:v{version}",
|
||||
jdbcPluginIncompatible: "协议不兼容",
|
||||
jdbcPluginNotInstalled: "未安装",
|
||||
jdbcDrivers: "JDBC 驱动 JAR",
|
||||
jdbcImport: "导入 JAR",
|
||||
jdbcImportPath: "导入路径",
|
||||
jdbcDriverPathPlaceholder: "/path/to/driver.jar",
|
||||
jdbcImportSuccess: "已导入 {count} 个驱动文件",
|
||||
jdbcDeleteSuccess: "驱动已删除",
|
||||
jdbcNoDrivers: "还没有导入 JDBC 驱动。",
|
||||
aboutDescription: "开源、轻量的数据库管理工具。",
|
||||
community: "社区",
|
||||
qqGroup: "QQ 群",
|
||||
|
||||
@@ -36,6 +36,13 @@ export const connectDb = forward("connectDb");
|
||||
export const disconnectDb = forward("disconnectDb");
|
||||
export const saveConnections = forward("saveConnections");
|
||||
export const loadConnections = forward("loadConnections");
|
||||
export const listPlugins = forward("listPlugins");
|
||||
export const listJdbcDrivers = forward("listJdbcDrivers");
|
||||
export const importJdbcDrivers = forward("importJdbcDrivers");
|
||||
export const deleteJdbcDriver = forward("deleteJdbcDriver");
|
||||
export const jdbcPluginStatus = forward("jdbcPluginStatus");
|
||||
export const installJdbcPlugin = forward("installJdbcPlugin");
|
||||
export const uninstallJdbcPlugin = forward("uninstallJdbcPlugin");
|
||||
export const loadSavedSqlLibrary = forward("loadSavedSqlLibrary");
|
||||
export const saveSavedSqlFolder = forward("saveSavedSqlFolder");
|
||||
export const deleteSavedSqlFolder = forward("deleteSavedSqlFolder");
|
||||
|
||||
@@ -7,6 +7,7 @@ export const SCHEMA_AWARE_TYPES = new Set<DatabaseType>([
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
"jdbc",
|
||||
]);
|
||||
|
||||
export const SQL_FILE_UNSUPPORTED_TYPES = new Set<DatabaseType>(["redis", "mongodb", "elasticsearch"]);
|
||||
@@ -78,7 +79,7 @@ export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set<DatabaseType>([
|
||||
|
||||
export const FETCH_FIRST_TYPES = new Set<DatabaseType>(["oracle", "dameng"]);
|
||||
|
||||
export const TREE_SCHEMA_TYPES = new Set<DatabaseType>(["postgres", "redshift", "sqlserver", "gaussdb"]);
|
||||
export const TREE_SCHEMA_TYPES = new Set<DatabaseType>(["postgres", "redshift", "sqlserver", "gaussdb", "jdbc"]);
|
||||
|
||||
export const PG_LIKE_STRUCTURE_TYPES = new Set<DatabaseType>(["postgres", "redshift", "gaussdb"]);
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import type {
|
||||
ForeignKeyInfo,
|
||||
TriggerInfo,
|
||||
QueryResult,
|
||||
InstalledPlugin,
|
||||
JdbcDriverInfo,
|
||||
JdbcPluginStatus,
|
||||
SidebarLayout,
|
||||
SavedSqlFile,
|
||||
SavedSqlFolder,
|
||||
@@ -91,6 +94,34 @@ export async function loadConnections(): Promise<ConnectionConfig[]> {
|
||||
return get("/api/connection/list");
|
||||
}
|
||||
|
||||
export async function listPlugins(): Promise<InstalledPlugin[]> {
|
||||
return get("/api/plugins");
|
||||
}
|
||||
|
||||
export async function listJdbcDrivers(): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function importJdbcDrivers(_paths: string[]): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function deleteJdbcDriver(_path: string): Promise<JdbcDriverInfo[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function jdbcPluginStatus(): Promise<JdbcPluginStatus> {
|
||||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return { installed: false, version: null, protocol_version: null, compatible: true, path: "" };
|
||||
}
|
||||
|
||||
export async function loadSavedSqlLibrary(): Promise<SavedSqlLibrary> {
|
||||
return get("/api/saved-sql");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import type {
|
||||
ForeignKeyInfo,
|
||||
TriggerInfo,
|
||||
QueryResult,
|
||||
InstalledPlugin,
|
||||
JdbcDriverInfo,
|
||||
JdbcPluginStatus,
|
||||
SavedSqlFile,
|
||||
SavedSqlFolder,
|
||||
SavedSqlLibrary,
|
||||
@@ -244,6 +247,34 @@ export async function loadConnections(): Promise<ConnectionConfig[]> {
|
||||
return invoke("load_connections");
|
||||
}
|
||||
|
||||
export async function listPlugins(): Promise<InstalledPlugin[]> {
|
||||
return invoke("list_plugins");
|
||||
}
|
||||
|
||||
export async function listJdbcDrivers(): Promise<JdbcDriverInfo[]> {
|
||||
return invoke("list_jdbc_drivers");
|
||||
}
|
||||
|
||||
export async function importJdbcDrivers(paths: string[]): Promise<JdbcDriverInfo[]> {
|
||||
return invoke("import_jdbc_drivers", { paths });
|
||||
}
|
||||
|
||||
export async function deleteJdbcDriver(path: string): Promise<JdbcDriverInfo[]> {
|
||||
return invoke("delete_jdbc_driver", { path });
|
||||
}
|
||||
|
||||
export async function jdbcPluginStatus(): Promise<JdbcPluginStatus> {
|
||||
return invoke("jdbc_plugin_status");
|
||||
}
|
||||
|
||||
export async function installJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return invoke("install_jdbc_plugin");
|
||||
}
|
||||
|
||||
export async function uninstallJdbcPlugin(): Promise<JdbcPluginStatus> {
|
||||
return invoke("uninstall_jdbc_plugin");
|
||||
}
|
||||
|
||||
export async function loadSavedSqlLibrary(): Promise<SavedSqlLibrary> {
|
||||
return invoke("load_saved_sql_library");
|
||||
}
|
||||
|
||||
+40
-1
@@ -13,7 +13,8 @@ export type DatabaseType =
|
||||
| "starrocks"
|
||||
| "redshift"
|
||||
| "dameng"
|
||||
| "gaussdb";
|
||||
| "gaussdb"
|
||||
| "jdbc";
|
||||
|
||||
export interface ConnectionConfig {
|
||||
id: string;
|
||||
@@ -40,6 +41,44 @@ export interface ConnectionConfig {
|
||||
ssl?: boolean;
|
||||
sysdba?: boolean;
|
||||
connection_string?: string;
|
||||
jdbc_driver_class?: string;
|
||||
jdbc_driver_paths?: string[];
|
||||
}
|
||||
|
||||
export interface PluginDriverManifest {
|
||||
id: string;
|
||||
label: string;
|
||||
kind: string;
|
||||
database_type?: string;
|
||||
}
|
||||
|
||||
export interface PluginManifest {
|
||||
id: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
protocol_version?: number;
|
||||
description?: string;
|
||||
executable?: string;
|
||||
drivers: PluginDriverManifest[];
|
||||
}
|
||||
|
||||
export interface InstalledPlugin {
|
||||
manifest: PluginManifest;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface JdbcDriverInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface JdbcPluginStatus {
|
||||
installed: boolean;
|
||||
version?: string | null;
|
||||
protocol_version?: number | null;
|
||||
compatible: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface DatabaseInfo {
|
||||
|
||||
Reference in New Issue
Block a user