From a435d62d3bd7e48bc880b4758aef386e4e37f549 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Thu, 26 Feb 2026 13:57:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(connection-modal):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9ESSH=E7=A7=81=E9=92=A5=E6=96=87=E4=BB=B6=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E9=80=89=E6=8B=A9=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增私钥文件选择入口,减少手动输入路径错误 - 复用系统文件对话框并自动回填私钥路径 - 保留手动输入作为兜底方式 - refs #119 --- frontend/src/components/ConnectionModal.tsx | 38 ++++++++++++++++-- frontend/wailsjs/go/app/App.d.ts | 2 + frontend/wailsjs/go/app/App.js | 4 ++ internal/app/methods_file.go | 43 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index f1b4112..0224149 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Modal, Form, Input, InputNumber, Button, message, Checkbox, Divider, Select, Alert, Card, Row, Col, Typography, Collapse, Space, Table, Tag } from 'antd'; import { DatabaseOutlined, ConsoleSqlOutlined, FileTextOutlined, CloudServerOutlined, AppstoreAddOutlined, CloudOutlined, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { useStore } from '../store'; -import { DBGetDatabases, GetDriverStatusList, MongoDiscoverMembers, TestConnection, RedisConnect } from '../../wailsjs/go/app/App'; +import { DBGetDatabases, GetDriverStatusList, MongoDiscoverMembers, TestConnection, RedisConnect, SelectSSHKeyFile } from '../../wailsjs/go/app/App'; import { MongoMemberInfo, SavedConnection } from '../types'; const { Meta } = Card; @@ -71,6 +71,7 @@ const ConnectionModal: React.FC<{ const [typeSelectWarning, setTypeSelectWarning] = useState<{ driverName: string; reason: string } | null>(null); const [driverStatusMap, setDriverStatusMap] = useState>({}); const [driverStatusLoaded, setDriverStatusLoaded] = useState(false); + const [selectingSSHKey, setSelectingSSHKey] = useState(false); const testInFlightRef = useRef(false); const testTimerRef = useRef(null); const addConnection = useStore((state) => state.addConnection); @@ -578,6 +579,30 @@ const ConnectionModal: React.FC<{ } }; + const handleSelectSSHKeyFile = async () => { + if (selectingSSHKey) { + return; + } + try { + setSelectingSSHKey(true); + const currentPath = String(form.getFieldValue('sshKeyPath') || '').trim(); + const res = await SelectSSHKeyFile(currentPath); + if (res?.success) { + const data = res.data || {}; + const selectedPath = typeof data === 'string' ? data : String(data.path || '').trim(); + if (selectedPath) { + form.setFieldValue('sshKeyPath', selectedPath); + } + } else if (res?.message !== 'Cancelled') { + message.error(`选择私钥文件失败: ${res?.message || '未知错误'}`); + } + } catch (e: any) { + message.error(`选择私钥文件失败: ${e?.message || String(e)}`); + } finally { + setSelectingSSHKey(false); + } + }; + useEffect(() => { if (open) { setTestResult(null); // Reset test result @@ -1493,8 +1518,15 @@ const ConnectionModal: React.FC<{ - - + + + + + + + )} diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index f954704..03d829f 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -156,6 +156,8 @@ export function SelectDriverDownloadDirectory(arg1:string):Promise; +export function SelectSSHKeyFile(arg1:string):Promise; + export function SetWindowTranslucency(arg1:number,arg2:number):Promise; export function TestConnection(arg1:connection.ConnectionConfig):Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index fee9789..23560b5 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -306,6 +306,10 @@ export function SelectDriverPackageFile(arg1) { return window['go']['app']['App']['SelectDriverPackageFile'](arg1); } +export function SelectSSHKeyFile(arg1) { + return window['go']['app']['App']['SelectSSHKeyFile'](arg1); +} + export function SetWindowTranslucency(arg1, arg2) { return window['go']['app']['App']['SetWindowTranslucency'](arg1, arg2); } diff --git a/internal/app/methods_file.go b/internal/app/methods_file.go index 556c5a3..67275dd 100644 --- a/internal/app/methods_file.go +++ b/internal/app/methods_file.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "os" + "path/filepath" "sort" "strconv" "strings" @@ -77,6 +78,48 @@ func (a *App) ImportConfigFile() connection.QueryResult { return connection.QueryResult{Success: true, Data: string(content)} } +func (a *App) SelectSSHKeyFile(currentPath string) connection.QueryResult { + defaultDir := strings.TrimSpace(currentPath) + if defaultDir == "" { + if home, err := os.UserHomeDir(); err == nil { + defaultDir = filepath.Join(home, ".ssh") + } + } + if filepath.Ext(defaultDir) != "" { + defaultDir = filepath.Dir(defaultDir) + } + if defaultDir != "" && !filepath.IsAbs(defaultDir) { + if abs, err := filepath.Abs(defaultDir); err == nil { + defaultDir = abs + } + } + + selection, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "选择 SSH 私钥文件", + DefaultDirectory: defaultDir, + Filters: []runtime.FileFilter{ + { + DisplayName: "私钥文件", + Pattern: "*.pem;*.key;*.ppk;*id_rsa*", + }, + { + DisplayName: "所有文件", + Pattern: "*", + }, + }, + }) + if err != nil { + return connection.QueryResult{Success: false, Message: err.Error()} + } + if strings.TrimSpace(selection) == "" { + return connection.QueryResult{Success: false, Message: "Cancelled"} + } + if abs, err := filepath.Abs(selection); err == nil { + selection = abs + } + return connection.QueryResult{Success: true, Data: map[string]interface{}{"path": selection}} +} + // PreviewImportFile 解析导入文件,返回字段列表、总行数、前 5 行预览数据 func (a *App) PreviewImportFile(filePath string) connection.QueryResult { if filePath == "" {