mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-30 08:20:33 +00:00
feat(proxy): 增加数据库和 AI 代理支持
This commit is contained in:
@@ -22,7 +22,7 @@ type DbOption = { value: string; label: string };
|
||||
type DbCategory = { key: string; title: string; options: DbOption[] };
|
||||
type DialogStep = "select" | "config";
|
||||
type DbPickerView = "icon" | "list";
|
||||
type ConfigTab = "connection" | "ssh";
|
||||
type ConfigTab = "connection" | "ssh" | "proxy";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
@@ -67,6 +67,12 @@ const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
|
||||
ssh_key_passphrase: "",
|
||||
ssh_expose_lan: false,
|
||||
ssh_connect_timeout_secs: 5,
|
||||
proxy_enabled: false,
|
||||
proxy_type: "socks5",
|
||||
proxy_host: "",
|
||||
proxy_port: 1080,
|
||||
proxy_username: "",
|
||||
proxy_password: "",
|
||||
ssl: false,
|
||||
connection_string: undefined,
|
||||
jdbc_driver_class: undefined,
|
||||
@@ -264,6 +270,12 @@ watch(
|
||||
ssh_key_passphrase: config.ssh_key_passphrase || "",
|
||||
ssh_expose_lan: config.ssh_expose_lan || false,
|
||||
ssh_connect_timeout_secs: config.ssh_connect_timeout_secs || 5,
|
||||
proxy_enabled: config.proxy_enabled || false,
|
||||
proxy_type: config.proxy_type || "socks5",
|
||||
proxy_host: config.proxy_host || "",
|
||||
proxy_port: config.proxy_port || 1080,
|
||||
proxy_username: config.proxy_username || "",
|
||||
proxy_password: config.proxy_password || "",
|
||||
ssl: config.ssl || false,
|
||||
connection_string: config.connection_string,
|
||||
jdbc_driver_class: config.jdbc_driver_class,
|
||||
@@ -408,6 +420,9 @@ const hasDbPickerResults = computed(() => filteredDbCategories.value.some((categ
|
||||
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
|
||||
const isJdbcConnection = computed(() => form.value.db_type === "jdbc");
|
||||
const canUseSsh = computed(() => form.value.db_type !== "sqlite" && form.value.db_type !== "jdbc");
|
||||
const canUseProxy = computed(
|
||||
() => form.value.db_type !== "sqlite" && form.value.db_type !== "duckdb" && form.value.db_type !== "jdbc",
|
||||
);
|
||||
const testResultMessage = computed(() => {
|
||||
if (!testResult.value) return "";
|
||||
return testResult.value.ok ? t("connection.testSuccess") : testResult.value.message;
|
||||
@@ -456,6 +471,8 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
const config: ConnectionConfig = { ...form.value, id };
|
||||
const sshTimeout = Number(config.ssh_connect_timeout_secs);
|
||||
config.ssh_connect_timeout_secs = Number.isFinite(sshTimeout) && sshTimeout > 0 ? sshTimeout : 5;
|
||||
const proxyPort = Number(config.proxy_port);
|
||||
config.proxy_port = Number.isFinite(proxyPort) && proxyPort > 0 ? proxyPort : 1080;
|
||||
if (config.db_type === "mongodb" && !mongoUseUrl.value) {
|
||||
config.connection_string = undefined;
|
||||
}
|
||||
@@ -534,6 +551,12 @@ watch(canUseSsh, (value) => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(canUseProxy, (value) => {
|
||||
if (!value && configTab.value === "proxy") {
|
||||
configTab.value = "connection";
|
||||
}
|
||||
});
|
||||
|
||||
async function save() {
|
||||
if (isSaving.value) return;
|
||||
isSaving.value = true;
|
||||
@@ -778,10 +801,11 @@ function openExternalUrl(url: string) {
|
||||
<template v-else>
|
||||
<div class="space-y-3">
|
||||
<Tabs v-model="configTab" class="min-h-0">
|
||||
<div v-if="canUseSsh" class="flex items-center justify-between border-b pb-2">
|
||||
<div v-if="canUseSsh || canUseProxy" class="flex items-center justify-between border-b pb-2">
|
||||
<TabsList>
|
||||
<TabsTrigger value="connection">{{ t("connection.basicTab") }}</TabsTrigger>
|
||||
<TabsTrigger value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="canUseSsh" value="ssh">{{ t("connection.sshTunnel") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="canUseProxy" value="proxy">{{ t("connection.proxy") }}</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
@@ -1202,6 +1226,68 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent v-if="canUseProxy" value="proxy" 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 text-xs">{{ t("connection.proxy") }}</Label>
|
||||
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" v-model="form.proxy_enabled" class="mr-0" />
|
||||
<span class="text-xs text-muted-foreground">{{ t("connection.proxyEnable") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.proxyType") }}</Label>
|
||||
<Select
|
||||
:model-value="form.proxy_type || 'socks5'"
|
||||
:disabled="!form.proxy_enabled"
|
||||
@update:model-value="(value: any) => (form.proxy_type = value)"
|
||||
>
|
||||
<SelectTrigger class="col-span-3 h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="socks5">SOCKS5</SelectItem>
|
||||
<SelectItem value="http">HTTP CONNECT</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.proxyHost") }}</Label>
|
||||
<Input
|
||||
v-model="form.proxy_host"
|
||||
class="col-span-2"
|
||||
placeholder="127.0.0.1"
|
||||
:disabled="!form.proxy_enabled"
|
||||
/>
|
||||
<Input
|
||||
v-model.number="form.proxy_port"
|
||||
type="number"
|
||||
class="col-span-1"
|
||||
:disabled="!form.proxy_enabled"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.proxyUsername") }}</Label>
|
||||
<Input
|
||||
v-model="form.proxy_username"
|
||||
class="col-span-3"
|
||||
:placeholder="t('connection.proxyUsernamePlaceholder')"
|
||||
:disabled="!form.proxy_enabled"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.proxyPassword") }}</Label>
|
||||
<Input
|
||||
v-model="form.proxy_password"
|
||||
type="password"
|
||||
class="col-span-3"
|
||||
:placeholder="t('connection.proxyPasswordPlaceholder')"
|
||||
:disabled="!form.proxy_enabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -173,6 +173,8 @@ const tempApiKey = ref(settings.aiConfig.apiKey);
|
||||
const tempEndpoint = ref(settings.aiConfig.endpoint);
|
||||
const tempModel = ref(settings.aiConfig.model);
|
||||
const tempApiStyle = ref<AiApiStyle>(settings.aiConfig.apiStyle || "completions");
|
||||
const tempProxyEnabled = ref(!!settings.aiConfig.proxyEnabled);
|
||||
const tempProxyUrl = ref(settings.aiConfig.proxyUrl || "");
|
||||
|
||||
const providerDefaults: Record<AiProvider, { endpoint: string; model: string }> = {
|
||||
claude: { endpoint: "https://api.anthropic.com/v1/messages", model: "claude-sonnet-4-20250514" },
|
||||
@@ -213,6 +215,8 @@ function openSettings() {
|
||||
tempEndpoint.value = settings.aiConfig.endpoint;
|
||||
tempModel.value = settings.aiConfig.model;
|
||||
tempApiStyle.value = settings.aiConfig.apiStyle || "completions";
|
||||
tempProxyEnabled.value = !!settings.aiConfig.proxyEnabled;
|
||||
tempProxyUrl.value = settings.aiConfig.proxyUrl || "";
|
||||
showSettings.value = true;
|
||||
}
|
||||
|
||||
@@ -223,6 +227,8 @@ function saveSettings() {
|
||||
endpoint: tempEndpoint.value,
|
||||
model: tempModel.value,
|
||||
apiStyle: tempApiStyle.value,
|
||||
proxyEnabled: tempProxyEnabled.value,
|
||||
proxyUrl: tempProxyUrl.value,
|
||||
});
|
||||
showSettings.value = false;
|
||||
}
|
||||
@@ -242,6 +248,8 @@ async function testAiConnection() {
|
||||
endpoint: tempEndpoint.value,
|
||||
model: tempModel.value,
|
||||
apiStyle: tempApiStyle.value,
|
||||
proxyEnabled: tempProxyEnabled.value,
|
||||
proxyUrl: tempProxyUrl.value,
|
||||
});
|
||||
testResult.value = "success";
|
||||
} catch (e: any) {
|
||||
@@ -799,6 +807,23 @@ function formatInlineText(text: string): string {
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxy") }}</Label>
|
||||
<label class="col-span-2 flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<input v-model="tempProxyEnabled" type="checkbox" class="h-4 w-4 shrink-0 accent-primary" />
|
||||
{{ t("ai.proxyEnable") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-3">
|
||||
<Label class="text-right text-xs">{{ t("ai.proxyUrl") }}</Label>
|
||||
<Input
|
||||
v-model="tempProxyUrl"
|
||||
autocomplete="off"
|
||||
class="col-span-2 h-8 text-xs"
|
||||
placeholder="socks5://127.0.0.1:7890"
|
||||
:disabled="!tempProxyEnabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter class="flex items-center gap-2">
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
|
||||
@@ -143,6 +143,14 @@ export default {
|
||||
sshKeyPathBrowse: "Browse",
|
||||
sshExposeLan: "Expose tunnel to LAN",
|
||||
sshConnectTimeout: "SSH Timeout (seconds)",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Connect database through proxy",
|
||||
proxyType: "Proxy Type",
|
||||
proxyHost: "Proxy Host",
|
||||
proxyUsername: "Proxy User",
|
||||
proxyUsernamePlaceholder: "Optional",
|
||||
proxyPassword: "Proxy Password",
|
||||
proxyPasswordPlaceholder: "Optional",
|
||||
jdbcUrl: "JDBC URL",
|
||||
jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/database",
|
||||
jdbcDriverClass: "Driver Class (optional)",
|
||||
@@ -467,6 +475,9 @@ export default {
|
||||
truncated: "Context truncated",
|
||||
contextSummary: "{database} · {tables} tables",
|
||||
autoSqlBlocked: "The AI-generated SQL looked too risky to auto-execute. Review it manually before running.",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Send AI requests through proxy",
|
||||
proxyUrl: "Proxy URL",
|
||||
settingsHint:
|
||||
"The config is stored in the local app data directory. Requests are sent by the Tauri backend instead of directly from the frontend.",
|
||||
actions: {
|
||||
|
||||
@@ -143,6 +143,14 @@ export default {
|
||||
sshKeyPathBrowse: "Examinar",
|
||||
sshExposeLan: "Exponer túnel a la red local",
|
||||
sshConnectTimeout: "Tiempo de espera SSH (segundos)",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Conectar la base de datos mediante proxy",
|
||||
proxyType: "Tipo de proxy",
|
||||
proxyHost: "Host del proxy",
|
||||
proxyUsername: "Usuario del proxy",
|
||||
proxyUsernamePlaceholder: "Opcional",
|
||||
proxyPassword: "Contraseña del proxy",
|
||||
proxyPasswordPlaceholder: "Opcional",
|
||||
jdbcUrl: "URL JDBC",
|
||||
jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/base_de_datos",
|
||||
jdbcDriverClass: "Clase del driver (opcional)",
|
||||
@@ -474,6 +482,9 @@ export default {
|
||||
contextSummary: "{database} · {tables} tablas",
|
||||
autoSqlBlocked:
|
||||
"El SQL generado por la IA parecía demasiado riesgoso para ejecutarse automáticamente. Revísalo manualmente antes de ejecutar.",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Enviar solicitudes de IA mediante proxy",
|
||||
proxyUrl: "URL del proxy",
|
||||
settingsHint:
|
||||
"La configuración se almacena en el directorio de datos local de la aplicación. Las solicitudes son enviadas por el backend de Tauri en lugar de directamente desde el frontend.",
|
||||
actions: {
|
||||
|
||||
@@ -142,6 +142,14 @@ export default {
|
||||
sshKeyPathBrowse: "浏览",
|
||||
sshExposeLan: "允许局域网访问隧道",
|
||||
sshConnectTimeout: "SSH 超时时间(秒)",
|
||||
proxy: "代理",
|
||||
proxyEnable: "通过代理连接数据库",
|
||||
proxyType: "代理类型",
|
||||
proxyHost: "代理主机",
|
||||
proxyUsername: "代理用户",
|
||||
proxyUsernamePlaceholder: "可选",
|
||||
proxyPassword: "代理密码",
|
||||
proxyPasswordPlaceholder: "可选",
|
||||
jdbcUrl: "JDBC URL",
|
||||
jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/database",
|
||||
jdbcDriverClass: "驱动类(可选)",
|
||||
@@ -458,6 +466,9 @@ export default {
|
||||
truncated: "上下文已截断",
|
||||
contextSummary: "{database} · {tables} 张表",
|
||||
autoSqlBlocked: "AI 生成的 SQL 风险较高,已阻止自动执行,请手动检查后再运行。",
|
||||
proxy: "代理",
|
||||
proxyEnable: "AI 请求通过代理发送",
|
||||
proxyUrl: "代理地址",
|
||||
settingsHint: "配置会保存在本机应用数据目录中。请求由 Tauri 后端发出,避免在前端直接暴露给模型服务。",
|
||||
actions: {
|
||||
generate: "生成 SQL",
|
||||
|
||||
@@ -171,6 +171,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
driver_label: config.driver_label || labelMap[profile] || config.db_type,
|
||||
url_params: config.url_params || "",
|
||||
ssh_connect_timeout_secs: config.ssh_connect_timeout_secs || 5,
|
||||
proxy_type: config.proxy_type || "socks5",
|
||||
proxy_port: config.proxy_port || 1080,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface AiConfig {
|
||||
endpoint: string;
|
||||
model: string;
|
||||
apiStyle: AiApiStyle;
|
||||
proxyEnabled?: boolean;
|
||||
proxyUrl?: string;
|
||||
}
|
||||
|
||||
const defaultConfigs: Record<AiProvider, Omit<AiConfig, "apiKey">> = {
|
||||
|
||||
@@ -38,6 +38,12 @@ export interface ConnectionConfig {
|
||||
ssh_key_passphrase?: string;
|
||||
ssh_expose_lan?: boolean;
|
||||
ssh_connect_timeout_secs?: number;
|
||||
proxy_enabled?: boolean;
|
||||
proxy_type?: "socks5" | "http";
|
||||
proxy_host?: string;
|
||||
proxy_port?: number;
|
||||
proxy_username?: string;
|
||||
proxy_password?: string;
|
||||
ssl?: boolean;
|
||||
sysdba?: boolean;
|
||||
connection_string?: string;
|
||||
|
||||
Reference in New Issue
Block a user