diff --git a/go-client/config.json b/go-client/config.json index 48160403..b5a260d6 100644 --- a/go-client/config.json +++ b/go-client/config.json @@ -496,6 +496,27 @@ "is_internal": false, "is_default": false, "is_set": false + }, + { + "name": "mysqlworkbench", + "display_name": "MySQL Workbench", + "protocol": [ + "mysql" + ], + "comment": { + "zh": "MySQL Workbench 是一款专为 MySQL 设计的集成可视化工具。\n\n!!!手动下载安装,点击保存启用!!!", + "en": "MySQL Workbench is a unified visual tool for database architects, developers, and DBAs.\n\n!!!Manually download and install, click Save to activate!!!" + }, + "download_url": "https://dev.mysql.com/downloads/workbench/", + "type": "databases", + "path": "C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe", + "arg_format": "", + "match_first": [ + "mysql" + ], + "is_internal": false, + "is_default": false, + "is_set": false } ] }, @@ -894,4 +915,4 @@ } ] } -} +} \ No newline at end of file diff --git a/go-client/pkg/awaken/awaken_windows.go b/go-client/pkg/awaken/awaken_windows.go index a7e0fd13..a9bd5d29 100755 --- a/go-client/pkg/awaken/awaken_windows.go +++ b/go-client/pkg/awaken/awaken_windows.go @@ -1,6 +1,7 @@ package awaken import ( + "crypto/rand" "encoding/json" "fmt" "go-client/global" @@ -25,6 +26,14 @@ func EnsureDirExist(path string) { } } +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + func getNavicatURL(connectInfo map[string]string) string { re := regexp.MustCompile(`@(.+)$`) matches := re.FindStringSubmatch(connectInfo["name"]) @@ -293,6 +302,83 @@ func handleSSH(r *Rouse, cfg *config.AppConfig) *exec.Cmd { } } +// prepareWorkbenchConnection writes a minimal connection entry into MySQL Workbench's +// connections.xml so that the connection succeeds without OS mismatch warnings. +func prepareWorkbenchConnection(connName, host, port, username string) error { + dir, err := os.UserConfigDir() + if err != nil { + return fmt.Errorf("failed to get user config dir: %w", err) + } + wbDir := filepath.Join(dir, "MySQL", "Workbench") + EnsureDirExist(wbDir) + connFile := filepath.Join(wbDir, "connections.xml") + + // Generate a UUID for the connection entry + b := make([]byte, 16) + rand.Read(b) + connID := fmt.Sprintf("{%08X-%04X-%04X-%04X-%012X}", + b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) + + // Build the minimal connection XML entry + connEntry := fmt.Sprintf(` + + com.mysql.rdbms.mysql.driver.native + Mysql@%s:%s + 0 + + + %s + %s + %s + + %s + `, + connID, host, port, host, port, username, connName) + + var data []byte + if !fileExists(connFile) { + content := fmt.Sprintf(` + + %s + +`, connEntry) + data = []byte(content) + } else { + existing, err := ioutil.ReadFile(connFile) + if err != nil { + return fmt.Errorf("failed to read connections.xml: %w", err) + } + content := string(existing) + + // Remove existing JumpServer connection if it exists + re := regexp.MustCompile(`(?s)]*>.*?JumpServer.*?`) + content = re.ReplaceAllString(content, "") + + insertPos := strings.Index(content, "\n") + if insertPos == -1 { + insertPos = strings.Index(content, "\r\n") + } + if insertPos != -1 { + content = content[:insertPos] + connEntry + "\n " + content[insertPos:] + data = []byte(content) + } else { + content := fmt.Sprintf(` + + %s + +`, connEntry) + data = []byte(content) + } + } + + err = ioutil.WriteFile(connFile, data, 0644) + if err != nil { + return fmt.Errorf("failed to write connections.xml: %w", err) + } + + return nil +} + func handleDB(r *Rouse, cfg *config.AppConfig) *exec.Cmd { var appItem *config.AppItem appLst := cfg.Windows.Databases @@ -375,6 +461,45 @@ func handleDB(r *Rouse, cfg *config.AppConfig) *exec.Cmd { url := getNavicatURL(connectMap) connectMap["url"] = url } + if appItem.Name == "mysqlworkbench" { + connName := "JumpServer" + err := prepareWorkbenchConnection(connName, r.Host, strconv.Itoa(r.Port), r.getUserName()) + if err != nil { + global.LOG.Error("Failed to prepare MySQL Workbench connection: " + err.Error()) + return nil + } + global.LOG.Info("MySQL Workbench connection prepared: " + connName) + + autoit.LoadAuto() + + // Run Workbench with --query to directly open the connection tab + global.LOG.Info("Launching MySQL Workbench with query: " + connName) + autoit.Run(fmt.Sprintf(`"%s" --query "%s"`, appPath, connName)) + + // Wait for password prompt dialog + // The title is "Connect to MySQL Server" + pwdTitle := "[REGEXPTITLE:.*Connect to MySQL Server.*]" + active := false + for i := 0; i <= 30; i++ { + ret := autoit.WinWaitActive(pwdTitle, "", 1) + time.Sleep(300 * time.Millisecond) + if ret != 0 { + active = true + break + } + autoit.WinActivate(pwdTitle, "") + } + if active { + time.Sleep(500 * time.Millisecond) + // Send password in raw mode and press Enter + autoit.Send(r.Value, 1) + time.Sleep(100 * time.Millisecond) + autoit.Send("{ENTER}") + } else { + global.LOG.Error("MySQL Workbench password dialog not detected") + } + return exec.Command("cmd", "/C", "exit", "0") + } if len(appItem.AutoIt) == 0 { commands := getCommandFromArgs(connectMap, appItem.ArgFormat) if appItem.Name == "heidisql" && r.Protocol == "postgresql" { diff --git a/ui/assets/images/mysqlworkbench.png b/ui/assets/images/mysqlworkbench.png new file mode 100644 index 00000000..8d928778 Binary files /dev/null and b/ui/assets/images/mysqlworkbench.png differ diff --git a/ui/components/SettingItems/settingItems.vue b/ui/components/SettingItems/settingItems.vue index 0f4995ad..f6654936 100644 --- a/ui/components/SettingItems/settingItems.vue +++ b/ui/components/SettingItems/settingItems.vue @@ -43,7 +43,8 @@ const imagesMap: Record = { navicat17: getImageByName("navicat17"), royalts: getImageByName("royalts"), windows_rdm: getImageByName("windows_rdm"), - toad: getImageByName("toad") + toad: getImageByName("toad"), + mysqlworkbench: getImageByName("mysqlworkbench") }; const commentText = computed(() => {