mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
Merge branch 'Syngnat:dev' into dev
This commit is contained in:
@@ -1 +1 @@
|
||||
26a843d5fd071d0c7e9d8022e98eb4e3
|
||||
571d014306268cf67665967059cda912
|
||||
@@ -1702,14 +1702,18 @@ function App() {
|
||||
const importKind = detectConnectionImportKind(raw);
|
||||
|
||||
if (importKind === 'invalid') {
|
||||
void message.error('文件格式错误:仅支持 GoNavi 恢复包或历史 JSON 连接数组');
|
||||
void message.error('文件格式错误:仅支持 GoNavi 恢复包、历史 JSON 连接数组或 MySQL Workbench XML');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setPendingConnectionImportPayload(null);
|
||||
const importedViews = await importConnectionsPayload(raw, '');
|
||||
void message.success(`成功导入 ${importedViews.length} 个连接`);
|
||||
if (importKind === 'mysql-workbench-xml' && importedViews.some(v => !v.hasPrimaryPassword)) {
|
||||
void message.warning(`成功导入 ${importedViews.length} 个连接,部分连接未包含密码,请编辑对应连接并输入密码后保存`);
|
||||
} else {
|
||||
void message.success(`成功导入 ${importedViews.length} 个连接`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (isConnectionPackagePasswordRequiredError(e)) {
|
||||
setPendingConnectionImportPayload(raw);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ConnectionConfig, SavedConnection } from '../types';
|
||||
|
||||
export type ConnectionImportKind = 'app-managed-package' | 'encrypted-package' | 'legacy-json' | 'invalid';
|
||||
export type ConnectionImportKind = 'app-managed-package' | 'encrypted-package' | 'legacy-json' | 'mysql-workbench-xml' | 'invalid';
|
||||
export type ConnectionPackageDialogSnapshot = {
|
||||
open: boolean;
|
||||
mode: 'export' | 'import';
|
||||
@@ -105,7 +105,15 @@ const parseConnectionImportRaw = (raw: unknown): unknown => {
|
||||
}
|
||||
};
|
||||
|
||||
const isMySQLWorkbenchXML = (raw: string): boolean => (
|
||||
raw.includes('<data') && raw.includes('grt_format') && raw.includes('db.mgmt.Connection')
|
||||
);
|
||||
|
||||
export const detectConnectionImportKind = (raw: unknown): ConnectionImportKind => {
|
||||
if (typeof raw === 'string' && isMySQLWorkbenchXML(raw)) {
|
||||
return 'mysql-workbench-xml';
|
||||
}
|
||||
|
||||
const parsed = parseConnectionImportRaw(raw);
|
||||
|
||||
if (isConnectionPackageV2AppManagedEnvelope(parsed)) {
|
||||
|
||||
@@ -54,4 +54,4 @@ describe('connectionModalPresentation', () => {
|
||||
shouldToast: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
83
frontend/wailsjs/runtime/runtime.d.ts
vendored
83
frontend/wailsjs/runtime/runtime.d.ts
vendored
@@ -246,4 +246,85 @@ export function OnFileDropOff() :void
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
|
||||
// Notification types
|
||||
export interface NotificationOptions {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string; // macOS and Linux only
|
||||
body?: string;
|
||||
categoryId?: string;
|
||||
data?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface NotificationAction {
|
||||
id?: string;
|
||||
title?: string;
|
||||
destructive?: boolean; // macOS-specific
|
||||
}
|
||||
|
||||
export interface NotificationCategory {
|
||||
id?: string;
|
||||
actions?: NotificationAction[];
|
||||
hasReplyField?: boolean;
|
||||
replyPlaceholder?: string;
|
||||
replyButtonTitle?: string;
|
||||
}
|
||||
|
||||
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
|
||||
// Initializes the notification service for the application.
|
||||
// This must be called before sending any notifications.
|
||||
export function InitializeNotifications(): Promise<void>;
|
||||
|
||||
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
|
||||
// Cleans up notification resources and releases any held connections.
|
||||
export function CleanupNotifications(): Promise<void>;
|
||||
|
||||
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
|
||||
// Checks if notifications are available on the current platform.
|
||||
export function IsNotificationAvailable(): Promise<boolean>;
|
||||
|
||||
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
|
||||
// Requests notification authorization from the user (macOS only).
|
||||
export function RequestNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
|
||||
// Checks the current notification authorization status (macOS only).
|
||||
export function CheckNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
|
||||
// Sends a basic notification with the given options.
|
||||
export function SendNotification(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
|
||||
// Sends a notification with action buttons. Requires a registered category.
|
||||
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
|
||||
// Registers a notification category that can be used with SendNotificationWithActions.
|
||||
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
|
||||
|
||||
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
|
||||
// Removes a previously registered notification category.
|
||||
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
|
||||
|
||||
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
|
||||
// Removes all pending notifications from the notification center.
|
||||
export function RemoveAllPendingNotifications(): Promise<void>;
|
||||
|
||||
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
|
||||
// Removes a specific pending notification by its identifier.
|
||||
export function RemovePendingNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
|
||||
// Removes all delivered notifications from the notification center.
|
||||
export function RemoveAllDeliveredNotifications(): Promise<void>;
|
||||
|
||||
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
|
||||
// Removes a specific delivered notification by its identifier.
|
||||
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
|
||||
// Removes a notification by its identifier (cross-platform convenience function).
|
||||
export function RemoveNotification(identifier: string): Promise<void>;
|
||||
@@ -239,4 +239,60 @@ export function CanResolveFilePaths() {
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
|
||||
export function InitializeNotifications() {
|
||||
return window.runtime.InitializeNotifications();
|
||||
}
|
||||
|
||||
export function CleanupNotifications() {
|
||||
return window.runtime.CleanupNotifications();
|
||||
}
|
||||
|
||||
export function IsNotificationAvailable() {
|
||||
return window.runtime.IsNotificationAvailable();
|
||||
}
|
||||
|
||||
export function RequestNotificationAuthorization() {
|
||||
return window.runtime.RequestNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function CheckNotificationAuthorization() {
|
||||
return window.runtime.CheckNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function SendNotification(options) {
|
||||
return window.runtime.SendNotification(options);
|
||||
}
|
||||
|
||||
export function SendNotificationWithActions(options) {
|
||||
return window.runtime.SendNotificationWithActions(options);
|
||||
}
|
||||
|
||||
export function RegisterNotificationCategory(category) {
|
||||
return window.runtime.RegisterNotificationCategory(category);
|
||||
}
|
||||
|
||||
export function RemoveNotificationCategory(categoryId) {
|
||||
return window.runtime.RemoveNotificationCategory(categoryId);
|
||||
}
|
||||
|
||||
export function RemoveAllPendingNotifications() {
|
||||
return window.runtime.RemoveAllPendingNotifications();
|
||||
}
|
||||
|
||||
export function RemovePendingNotification(identifier) {
|
||||
return window.runtime.RemovePendingNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveAllDeliveredNotifications() {
|
||||
return window.runtime.RemoveAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
export function RemoveDeliveredNotification(identifier) {
|
||||
return window.runtime.RemoveDeliveredNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveNotification(identifier) {
|
||||
return window.runtime.RemoveNotification(identifier);
|
||||
}
|
||||
3
go.mod
3
go.mod
@@ -15,7 +15,7 @@ require (
|
||||
github.com/redis/go-redis/v9 v9.17.3
|
||||
github.com/sijms/go-ora/v2 v2.9.0
|
||||
github.com/taosdata/driver-go/v3 v3.7.8
|
||||
github.com/wailsapp/wails/v2 v2.11.0
|
||||
github.com/wailsapp/wails/v2 v2.12.0
|
||||
github.com/xuri/excelize/v2 v2.10.0
|
||||
go.mongodb.org/mongo-driver v1.17.9
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
@@ -27,6 +27,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,5 +1,7 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
|
||||
gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3 h1:QjslQNaH5Nuap5i4nijS0OYV6GMk5kqrAmgU90zBKd4=
|
||||
gitea.com/kingbase/gokb v0.0.0-20201021123113-29bd62a876c3/go.mod h1:7lH5A1jzCXD9Nl16DzaBUOfDAT8NPrDmZwKu1p5wf94=
|
||||
gitee.com/chunanyong/dm v1.8.22 h1:H7fsrnUIvEA0jlDWew7vwELry1ff+tLMIu2Fk2cIBSg=
|
||||
@@ -243,8 +245,8 @@ github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6N
|
||||
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
|
||||
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
|
||||
github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c=
|
||||
github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
|
||||
@@ -2,8 +2,10 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -268,6 +270,21 @@ func (a *App) ImportConnectionsPayload(raw string, password string) ([]connectio
|
||||
return sanitizeSavedConnectionViews(views), nil
|
||||
}
|
||||
|
||||
if isMySQLWorkbenchXML(trimmed) {
|
||||
inputs, err := parseMySQLWorkbenchXML(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析 MySQL Workbench XML 失败: %w", err)
|
||||
}
|
||||
if len(inputs) == 0 {
|
||||
return nil, fmt.Errorf("未在 XML 中找到有效的连接配置")
|
||||
}
|
||||
views, err := a.importSavedConnectionsAtomically(inputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sanitizeSavedConnectionViews(views), nil
|
||||
}
|
||||
|
||||
var legacy []connection.LegacySavedConnection
|
||||
if err := json.Unmarshal([]byte(trimmed), &legacy); err != nil {
|
||||
return nil, errConnectionPackageUnsupported
|
||||
@@ -372,3 +389,126 @@ func (s connectionPackageImportRollbackSnapshot) restore(a *App) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- MySQL Workbench XML import ---
|
||||
|
||||
func isMySQLWorkbenchXML(content string) bool {
|
||||
return strings.Contains(content, "<data") && strings.Contains(content, "grt_format") && strings.Contains(content, "db.mgmt.Connection")
|
||||
}
|
||||
|
||||
// mysqlWorkbenchData is the root XML element.
|
||||
type mysqlWorkbenchData struct {
|
||||
XMLName xml.Name `xml:"data"`
|
||||
Value mysqlWorkbenchTopValue `xml:"value"`
|
||||
}
|
||||
|
||||
type mysqlWorkbenchTopValue struct {
|
||||
Values []mysqlWorkbenchConnection `xml:"value"`
|
||||
}
|
||||
|
||||
type mysqlWorkbenchConnection struct {
|
||||
StructName string `xml:"struct-name,attr"`
|
||||
Values []mysqlWorkbenchValue `xml:"value"`
|
||||
Links []mysqlWorkbenchLinkValue `xml:"link"`
|
||||
}
|
||||
|
||||
type mysqlWorkbenchValue struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Key string `xml:"key,attr"`
|
||||
StructName string `xml:"struct-name,attr"`
|
||||
Content string `xml:",chardata"`
|
||||
Children []mysqlWorkbenchValue `xml:"value"`
|
||||
}
|
||||
|
||||
type mysqlWorkbenchLinkValue struct {
|
||||
Key string `xml:"key,attr"`
|
||||
Content string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func parseMySQLWorkbenchXML(content string) ([]connection.SavedConnectionInput, error) {
|
||||
var data mysqlWorkbenchData
|
||||
if err := xml.Unmarshal([]byte(content), &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inputs []connection.SavedConnectionInput
|
||||
for _, conn := range data.Value.Values {
|
||||
if conn.StructName != "db.mgmt.Connection" {
|
||||
continue
|
||||
}
|
||||
|
||||
input := parseMySQLWorkbenchConnection(conn)
|
||||
inputs = append(inputs, input)
|
||||
}
|
||||
return inputs, nil
|
||||
}
|
||||
|
||||
func parseMySQLWorkbenchConnection(conn mysqlWorkbenchConnection) connection.SavedConnectionInput {
|
||||
params := make(map[string]string)
|
||||
connName := ""
|
||||
driverKey := ""
|
||||
|
||||
for _, v := range conn.Values {
|
||||
key := strings.TrimSpace(v.Key)
|
||||
switch {
|
||||
case key == "name" && v.Type == "string":
|
||||
connName = strings.TrimSpace(v.Content)
|
||||
case key == "parameterValues" && v.Type == "dict":
|
||||
for _, child := range v.Children {
|
||||
childKey := strings.TrimSpace(child.Key)
|
||||
if childKey == "" {
|
||||
continue
|
||||
}
|
||||
params[childKey] = strings.TrimSpace(child.Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range conn.Links {
|
||||
if strings.TrimSpace(link.Key) == "driver" {
|
||||
driverKey = strings.TrimSpace(link.Content)
|
||||
}
|
||||
}
|
||||
|
||||
host := params["hostName"]
|
||||
port := 3306
|
||||
if p, err := strconv.Atoi(params["port"]); err == nil && p > 0 {
|
||||
port = p
|
||||
}
|
||||
user := params["userName"]
|
||||
schema := params["schema"]
|
||||
password := params["password"]
|
||||
|
||||
useSSL := false
|
||||
if v, err := strconv.Atoi(params["useSSL"]); err == nil && v > 0 {
|
||||
useSSL = true
|
||||
}
|
||||
|
||||
dbType := "mysql"
|
||||
if strings.Contains(driverKey, "mariadb") {
|
||||
dbType = "mariadb"
|
||||
}
|
||||
|
||||
connID := "conn-" + uuid.New().String()[:8]
|
||||
|
||||
config := connection.ConnectionConfig{
|
||||
ID: connID,
|
||||
Type: dbType,
|
||||
Host: host,
|
||||
Port: port,
|
||||
User: user,
|
||||
Password: password,
|
||||
Database: schema,
|
||||
UseSSL: useSSL,
|
||||
}
|
||||
|
||||
if connName == "" {
|
||||
connName = fmt.Sprintf("%s@%s:%d", user, host, port)
|
||||
}
|
||||
|
||||
return connection.SavedConnectionInput{
|
||||
ID: connID,
|
||||
Name: connName,
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func (a *App) resolveConnectionSecrets(config connection.ConnectionConfig) (conn
|
||||
}
|
||||
resolved := mergeConnectionSecretBundleIntoConfig(base, bundle)
|
||||
resolved.ID = view.ID
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,10 @@ func (a *App) ImportConfigFile() connection.QueryResult {
|
||||
DisplayName: "JSON Files (*.json)",
|
||||
Pattern: "*.json",
|
||||
},
|
||||
{
|
||||
DisplayName: "MySQL Workbench Connections (*.xml)",
|
||||
Pattern: "*.xml",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user