From 7c8ce0154be48cce54cf7a6817579d4d54a0884f Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 21 Apr 2026 10:19:02 +0700 Subject: [PATCH 1/3] fix: resolve bare SSH key filenames to ~/.ssh/ paths in importers --- .../Services/Export/ForeignApp/DBeaverImporter.swift | 3 ++- .../Export/ForeignApp/ForeignAppImporter.swift | 10 ++++++++++ .../Services/Export/ForeignApp/SequelAceImporter.swift | 3 ++- .../Services/Export/ForeignApp/TablePlusImporter.swift | 3 ++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift index b3ac1617a..442550f5b 100644 --- a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift @@ -204,7 +204,8 @@ struct DBeaverImporter: ForeignAppImporter { } let username = properties["username"] as? String ?? "" let authType = properties["authType"] as? String ?? "PASSWORD" - let keyPath = properties["keyPath"] as? String ?? "" + let rawKeyPath = properties["keyPath"] as? String ?? "" + let keyPath = ForeignAppPathHelper.resolveKeyPath(rawKeyPath) let authMethod: String switch authType { diff --git a/TablePro/Core/Services/Export/ForeignApp/ForeignAppImporter.swift b/TablePro/Core/Services/Export/ForeignApp/ForeignAppImporter.swift index 401435cc5..ef8cd53c5 100644 --- a/TablePro/Core/Services/Export/ForeignApp/ForeignAppImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/ForeignAppImporter.swift @@ -58,6 +58,16 @@ enum ForeignAppImporterRegistry { ] } +// MARK: - Path Helpers + +enum ForeignAppPathHelper { + static func resolveKeyPath(_ path: String) -> String { + guard !path.isEmpty else { return "" } + if path.hasPrefix("/") || path.hasPrefix("~/") { return path } + return "~/.ssh/\(path)" + } +} + // MARK: - Keychain Reader enum ForeignKeychainReader { diff --git a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift index 790360af3..4001323d9 100644 --- a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift @@ -191,7 +191,8 @@ struct SequelAceImporter: ForeignAppImporter { let portString = entry["sshPort"] as? String ?? "22" let port = Int(portString) ?? 22 let keyEnabled = (entry["sshKeyLocationEnabled"] as? Int ?? 0) != 0 - let keyPath = entry["sshKeyLocation"] as? String ?? "" + let rawKeyPath = entry["sshKeyLocation"] as? String ?? "" + let keyPath = ForeignAppPathHelper.resolveKeyPath(rawKeyPath) return ExportableSSHConfig( enabled: true, diff --git a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift index 4deb0229e..8a655366d 100644 --- a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift @@ -173,7 +173,8 @@ struct TablePlusImporter: ForeignAppImporter { let port = Int(portString) ?? 22 let username = entry["ServerUser"] as? String ?? "" let useKey = entry["isUsePrivateKey"] as? Bool ?? false - let keyPath = entry["ServerPrivateKeyName"] as? String ?? "" + let rawKeyPath = entry["ServerPrivateKeyName"] as? String ?? "" + let keyPath = ForeignAppPathHelper.resolveKeyPath(rawKeyPath) return ExportableSSHConfig( enabled: true, From 51c96f1c2bfd178caa702a576a42e07ff1c6dc2b Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 21 Apr 2026 10:26:47 +0700 Subject: [PATCH 2/3] fix: correct SSL mode and color raw value case to match enum definitions --- .../Export/ForeignApp/DBeaverImporter.swift | 12 ++++---- .../Export/ForeignApp/SequelAceImporter.swift | 28 +++++++++++-------- .../Export/ForeignApp/TablePlusImporter.swift | 20 ++++++++----- .../ForeignApp/DBeaverImporterTests.swift | 12 ++++---- .../ForeignApp/SequelAceImporterTests.swift | 18 ++++++------ .../ForeignApp/TablePlusImporterTests.swift | 10 +++---- 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift index 442550f5b..6acc109a5 100644 --- a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift @@ -239,12 +239,12 @@ struct DBeaverImporter: ForeignAppImporter { guard components.count >= 3 else { return nil } let (r, g, b) = (components[0], components[1], components[2]) - if r > 200 && g < 100 && b < 100 { return "red" } - if r > 200 && g > 100 && g < 200 && b < 100 { return "orange" } - if r > 200 && g > 200 && b < 100 { return "yellow" } - if r < 100 && g > 150 && b < 100 { return "green" } - if r < 100 && g < 100 && b > 200 { return "blue" } - if r > 100 && g < 100 && b > 150 { return "purple" } + if r > 200 && g < 100 && b < 100 { return "Red" } + if r > 200 && g > 100 && g < 200 && b < 100 { return "Orange" } + if r > 200 && g > 200 && b < 100 { return "Yellow" } + if r < 100 && g > 150 && b < 100 { return "Green" } + if r < 100 && g < 100 && b > 200 { return "Blue" } + if r > 100 && g < 100 && b > 150 { return "Purple" } return nil } diff --git a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift index 4001323d9..d04e1f893 100644 --- a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift @@ -151,8 +151,14 @@ struct SequelAceImporter: ForeignAppImporter { ) throws -> ExportableConnection { let name = entry["name"] as? String ?? "Untitled" let host = entry["host"] as? String ?? "localhost" - let portString = entry["port"] as? String ?? "" - let port = Int(portString) ?? 3306 + let port: Int + if let intPort = entry["port"] as? Int { + port = intPort + } else if let strPort = entry["port"] as? String, let parsed = Int(strPort) { + port = parsed + } else { + port = 3306 + } let username = entry["user"] as? String ?? "" let database = entry["database"] as? String ?? "" @@ -223,7 +229,7 @@ struct SequelAceImporter: ForeignAppImporter { guard useSSL else { return nil } return ExportableSSLConfig( - mode: "required", + mode: "Required", caCertificatePath: entry["sslCACertFileLocation"] as? String, clientCertificatePath: entry["sslCertificateFileLocation"] as? String, clientKeyPath: entry["sslKeyFileLocation"] as? String @@ -263,14 +269,14 @@ struct SequelAceImporter: ForeignAppImporter { private func mapColorIndex(_ index: Int) -> String? { switch index { - case 0: return "red" - case 1: return "orange" - case 2: return "yellow" - case 3: return "green" - case 4: return "blue" - case 5: return "purple" - case 6: return "pink" - case 7: return "gray" + case 0: return "Red" + case 1: return "Orange" + case 2: return "Yellow" + case 3: return "Green" + case 4: return "Blue" + case 5: return "Purple" + case 6: return "Pink" + case 7: return "Gray" default: return nil } } diff --git a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift index 8a655366d..b5aff3d20 100644 --- a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift @@ -124,8 +124,14 @@ struct TablePlusImporter: ForeignAppImporter { let dbType = mapDriver(driverString) let host = entry["DatabaseHost"] as? String ?? "localhost" - let portString = entry["DatabasePort"] as? String ?? "" - let port = Int(portString) ?? defaultPort(for: dbType) + let port: Int + if let intPort = entry["DatabasePort"] as? Int { + port = intPort + } else if let strPort = entry["DatabasePort"] as? String, let parsed = Int(strPort) { + port = parsed + } else { + port = defaultPort(for: dbType) + } let username = entry["DatabaseUser"] as? String ?? "" let database: String if dbType == "SQLite" { @@ -199,7 +205,7 @@ struct TablePlusImporter: ForeignAppImporter { let paths = entry["TlsKeyPaths"] as? [String] ?? [] return ExportableSSLConfig( - mode: "required", + mode: "Required", caCertificatePath: paths.count > 0 ? paths[0] : nil, clientCertificatePath: paths.count > 1 ? paths[1] : nil, clientKeyPath: paths.count > 2 ? paths[2] : nil @@ -252,10 +258,10 @@ struct TablePlusImporter: ForeignAppImporter { private func mapEnvironmentColor(_ environment: String?) -> String? { switch environment { - case "staging": return "yellow" - case "production": return "red" - case "testing": return "blue" - case "development": return "green" + case "staging": return "Yellow" + case "production": return "Red" + case "testing": return "Blue" + case "development": return "Green" default: return nil } } diff --git a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift index 9eef90030..0dc470a89 100644 --- a/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift +++ b/TableProTests/Core/Services/ForeignApp/DBeaverImporterTests.swift @@ -457,12 +457,12 @@ struct DBeaverImporterTests { let result = try importer.importConnections(includePasswords: false) let colorMap = Dictionary(uniqueKeysWithValues: result.envelope.connections.map { ($0.name, $0.color) }) - #expect(colorMap["Red"] == "red") - #expect(colorMap["Orange"] == "orange") - #expect(colorMap["Yellow"] == "yellow") - #expect(colorMap["Green"] == "green") - #expect(colorMap["Blue"] == "blue") - #expect(colorMap["Purple"] == "purple") + #expect(colorMap["Red"] == "Red") + #expect(colorMap["Orange"] == "Orange") + #expect(colorMap["Yellow"] == "Yellow") + #expect(colorMap["Green"] == "Green") + #expect(colorMap["Blue"] == "Blue") + #expect(colorMap["Purple"] == "Purple") #expect(colorMap["No Color"] == Optional.none) } diff --git a/TableProTests/Core/Services/ForeignApp/SequelAceImporterTests.swift b/TableProTests/Core/Services/ForeignApp/SequelAceImporterTests.swift index fc5f05475..2419b2fc0 100644 --- a/TableProTests/Core/Services/ForeignApp/SequelAceImporterTests.swift +++ b/TableProTests/Core/Services/ForeignApp/SequelAceImporterTests.swift @@ -214,7 +214,7 @@ struct SequelAceImporterTests { let ssl = result.envelope.connections[0].sslConfig #expect(ssl != nil) - #expect(ssl?.mode == "required") + #expect(ssl?.mode == "Required") #expect(ssl?.caCertificatePath == "/path/to/ca.pem") #expect(ssl?.clientCertificatePath == "/path/to/client-cert.pem") #expect(ssl?.clientKeyPath == "/path/to/client-key.pem") @@ -264,14 +264,14 @@ struct SequelAceImporterTests { @Test("importConnections color index mapping") func testImportConnections_colorIndexMapping() throws { let colorMappings: [(Int, String?)] = [ - (0, "red"), - (1, "orange"), - (2, "yellow"), - (3, "green"), - (4, "blue"), - (5, "purple"), - (6, "pink"), - (7, "gray"), + (0, "Red"), + (1, "Orange"), + (2, "Yellow"), + (3, "Green"), + (4, "Blue"), + (5, "Purple"), + (6, "Pink"), + (7, "Gray"), (-1, nil), (99, nil) ] diff --git a/TableProTests/Core/Services/ForeignApp/TablePlusImporterTests.swift b/TableProTests/Core/Services/ForeignApp/TablePlusImporterTests.swift index 581a39b2f..e0eae5e2c 100644 --- a/TableProTests/Core/Services/ForeignApp/TablePlusImporterTests.swift +++ b/TableProTests/Core/Services/ForeignApp/TablePlusImporterTests.swift @@ -231,7 +231,7 @@ struct TablePlusImporterTests { let ssl = conn.sslConfig #expect(ssl != nil) - #expect(ssl?.mode == "required") + #expect(ssl?.mode == "Required") #expect(ssl?.caCertificatePath == "/path/to/ca.pem") #expect(ssl?.clientCertificatePath == "/path/to/client-cert.pem") #expect(ssl?.clientKeyPath == "/path/to/client-key.pem") @@ -369,10 +369,10 @@ struct TablePlusImporterTests { let result = try importer.importConnections(includePasswords: false) let connections = result.envelope.connections - #expect(connections[0].color == "yellow") - #expect(connections[1].color == "red") - #expect(connections[2].color == "blue") - #expect(connections[3].color == "green") + #expect(connections[0].color == "Yellow") + #expect(connections[1].color == "Red") + #expect(connections[2].color == "Blue") + #expect(connections[3].color == "Green") #expect(connections[4].color == nil) } From 93c27982d687df6cc5aa69b9c3e1e6ff1f7670aa Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 21 Apr 2026 10:46:09 +0700 Subject: [PATCH 3/3] fix: enable useSSHConfig for imported SSH connections --- TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift | 2 +- .../Core/Services/Export/ForeignApp/SequelAceImporter.swift | 2 +- .../Core/Services/Export/ForeignApp/TablePlusImporter.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift index 6acc109a5..df403acc5 100644 --- a/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/DBeaverImporter.swift @@ -221,7 +221,7 @@ struct DBeaverImporter: ForeignAppImporter { username: username, authMethod: authMethod, privateKeyPath: authType == "PUBLIC_KEY" ? keyPath : "", - useSSHConfig: false, + useSSHConfig: true, agentSocketPath: "", jumpHosts: nil, totpMode: nil, diff --git a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift index d04e1f893..00034c7c1 100644 --- a/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/SequelAceImporter.swift @@ -207,7 +207,7 @@ struct SequelAceImporter: ForeignAppImporter { username: user, authMethod: keyEnabled ? "Private Key" : "Password", privateKeyPath: keyEnabled ? keyPath : "", - useSSHConfig: false, + useSSHConfig: true, agentSocketPath: "", jumpHosts: nil, totpMode: nil, diff --git a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift index b5aff3d20..b30a95779 100644 --- a/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift +++ b/TablePro/Core/Services/Export/ForeignApp/TablePlusImporter.swift @@ -189,7 +189,7 @@ struct TablePlusImporter: ForeignAppImporter { username: username, authMethod: useKey ? "Private Key" : "Password", privateKeyPath: useKey ? keyPath : "", - useSSHConfig: false, + useSSHConfig: true, agentSocketPath: "", jumpHosts: nil, totpMode: nil,