store connection secrets in system keyring
This commit is contained in:
parent
3f71cd6dfa
commit
d14329ea15
|
|
@ -355,6 +355,59 @@ dependencies = [
|
|||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.1.4",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-native-tls"
|
||||
version = "0.4.0"
|
||||
|
|
@ -367,6 +420,59 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 1.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 1.1.4",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
|
|
@ -589,6 +695,19 @@ dependencies = [
|
|||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blowfish"
|
||||
version = "0.9.1"
|
||||
|
|
@ -1398,6 +1517,35 @@ version = "2.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus-secret-service"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||
dependencies = [
|
||||
"aes 0.8.4",
|
||||
"block-padding 0.3.3",
|
||||
"cbc 0.1.2",
|
||||
"dbus",
|
||||
"fastrand",
|
||||
"hkdf 0.12.4",
|
||||
"num",
|
||||
"once_cell",
|
||||
"sha2 0.10.9",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbx"
|
||||
version = "0.3.4"
|
||||
|
|
@ -1406,6 +1554,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"duckdb",
|
||||
"futures",
|
||||
"keyring",
|
||||
"log",
|
||||
"mongodb",
|
||||
"oracle-rs",
|
||||
|
|
@ -1810,6 +1959,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enum-as-inner"
|
||||
version = "0.6.1"
|
||||
|
|
@ -1841,6 +1996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1913,6 +2069,16 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
|
|
@ -2154,6 +2320,19 @@ version = "0.3.32"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
|
|
@ -2626,6 +2805,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
|
|
@ -3329,6 +3514,23 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
"linux-keyutils",
|
||||
"log",
|
||||
"secret-service",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"windows-sys 0.60.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
|
|
@ -3443,6 +3645,15 @@ version = "0.2.186"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libduckdb-sys"
|
||||
version = "1.10502.0"
|
||||
|
|
@ -3499,6 +3710,16 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-keyutils"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
|
|
@ -3851,7 +4072,7 @@ dependencies = [
|
|||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
|
@ -3892,6 +4113,19 @@ version = "1.0.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.2"
|
||||
|
|
@ -3910,6 +4144,20 @@ version = "0.1.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
|
|
@ -3972,6 +4220,17 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
|
@ -4248,6 +4507,16 @@ dependencies = [
|
|||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
|
|
@ -4626,6 +4895,17 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
|
|
@ -4741,6 +5021,20 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
|
|
@ -5576,7 +5870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "36140e8a20297bc2e8338807c3d9ca911f7fa49d7539cbcd6d48d3befd70efd8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nix",
|
||||
"nix 0.31.2",
|
||||
"ssh-encoding",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
|
@ -5707,7 +6001,7 @@ dependencies = [
|
|||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5890,6 +6184,38 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
|
||||
dependencies = [
|
||||
"aes 0.8.4",
|
||||
"cbc 0.1.2",
|
||||
"futures-util",
|
||||
"generic-array 0.14.7",
|
||||
"hkdf 0.12.4",
|
||||
"num",
|
||||
"once_cell",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
|
|
@ -6646,6 +6972,12 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
|
|
@ -7723,6 +8055,17 @@ version = "1.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
|
|
@ -9050,6 +9393,16 @@ dependencies = [
|
|||
"rustix 1.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
|
|
@ -9073,6 +9426,62 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix 0.29.0",
|
||||
"ordered-stream",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1 0.10.6",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
|
|
@ -9119,6 +9528,20 @@ name = "zeroize"
|
|||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
|
|
@ -9190,3 +9613,40 @@ dependencies = [
|
|||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -47,3 +47,4 @@ mongodb = "3.2.5"
|
|||
oracle-rs = "0.1"
|
||||
percent-encoding = "2"
|
||||
russh = "0.60"
|
||||
keyring = { version = "3.6.3", features = ["apple-native", "windows-native", "linux-native-sync-persistent", "crypto-rust"] }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ use std::sync::Arc;
|
|||
use tauri::{AppHandle, Manager, State};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::commands::connection_secrets::{
|
||||
load_connections_from_file, save_connections_to_file, KeyringConnectionSecretStore,
|
||||
};
|
||||
use crate::commands::query_cancel::RunningQueries;
|
||||
use crate::db;
|
||||
use crate::db::ssh_tunnel::TunnelManager;
|
||||
|
|
@ -230,21 +233,15 @@ pub async fn save_connections(
|
|||
configs: Vec<ConnectionConfig>,
|
||||
) -> Result<(), String> {
|
||||
let path = connections_file(&app)?;
|
||||
let json = serde_json::to_string_pretty(&configs).map_err(|e| e.to_string())?;
|
||||
std::fs::write(path, json).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
let store = KeyringConnectionSecretStore;
|
||||
save_connections_to_file(&path, &configs, &store)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_connections(app: AppHandle) -> Result<Vec<ConnectionConfig>, String> {
|
||||
let path = connections_file(&app)?;
|
||||
if !path.exists() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let json = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
let configs: Vec<ConnectionConfig> =
|
||||
serde_json::from_str(&json).map_err(|e| e.to_string())?;
|
||||
Ok(configs)
|
||||
let store = KeyringConnectionSecretStore;
|
||||
load_connections_from_file(&path, &store)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,419 @@
|
|||
use crate::models::connection::ConnectionConfig;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
|
||||
pub(super) const MAIN_PASSWORD_KEY: &str = "password";
|
||||
pub(super) const SSH_PASSWORD_KEY: &str = "ssh_password";
|
||||
pub(super) const CONNECTION_STRING_KEY: &str = "connection_string";
|
||||
|
||||
const KEYRING_SERVICE: &str = "dev.dbx.connections";
|
||||
|
||||
pub(super) trait ConnectionSecretStore {
|
||||
fn set_secret(&self, connection_id: &str, key: &str, secret: &str) -> Result<(), String>;
|
||||
fn get_secret(&self, connection_id: &str, key: &str) -> Result<Option<String>, String>;
|
||||
fn delete_secret(&self, connection_id: &str, key: &str) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub(super) struct KeyringConnectionSecretStore;
|
||||
|
||||
impl ConnectionSecretStore for KeyringConnectionSecretStore {
|
||||
fn set_secret(&self, connection_id: &str, key: &str, secret: &str) -> Result<(), String> {
|
||||
let entry = keyring_entry(connection_id, key, "create")?;
|
||||
entry
|
||||
.set_password(secret)
|
||||
.map_err(|err| keyring_error("store", connection_id, key, err))
|
||||
}
|
||||
|
||||
fn get_secret(&self, connection_id: &str, key: &str) -> Result<Option<String>, String> {
|
||||
let entry = keyring_entry(connection_id, key, "create")?;
|
||||
match entry.get_password() {
|
||||
Ok(secret) => Ok(Some(secret)),
|
||||
Err(keyring::Error::NoEntry) => Ok(None),
|
||||
Err(err) => Err(keyring_error("read", connection_id, key, err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_secret(&self, connection_id: &str, key: &str) -> Result<(), String> {
|
||||
let entry = keyring_entry(connection_id, key, "create")?;
|
||||
match entry.delete_credential() {
|
||||
Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
|
||||
Err(err) => Err(keyring_error("delete", connection_id, key, err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn save_connections_to_file(
|
||||
path: &Path,
|
||||
configs: &[ConnectionConfig],
|
||||
store: &impl ConnectionSecretStore,
|
||||
) -> Result<(), String> {
|
||||
delete_removed_connection_secrets(path, configs, store)?;
|
||||
for config in configs {
|
||||
persist_secret(store, &config.id, MAIN_PASSWORD_KEY, &config.password)?;
|
||||
persist_secret(store, &config.id, SSH_PASSWORD_KEY, &config.ssh_password)?;
|
||||
persist_optional_secret(
|
||||
store,
|
||||
&config.id,
|
||||
CONNECTION_STRING_KEY,
|
||||
config.connection_string.as_deref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
write_sanitized_connections(path, configs)
|
||||
}
|
||||
|
||||
pub(super) fn load_connections_from_file(
|
||||
path: &Path,
|
||||
store: &impl ConnectionSecretStore,
|
||||
) -> Result<Vec<ConnectionConfig>, String> {
|
||||
if !path.exists() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut configs = read_connections(path)?;
|
||||
let mut needs_rewrite = false;
|
||||
for config in &mut configs {
|
||||
if config.password.is_empty() {
|
||||
if let Some(secret) = store.get_secret(&config.id, MAIN_PASSWORD_KEY)? {
|
||||
config.password = secret;
|
||||
}
|
||||
} else {
|
||||
store.set_secret(&config.id, MAIN_PASSWORD_KEY, &config.password)?;
|
||||
needs_rewrite = true;
|
||||
}
|
||||
|
||||
if config.ssh_password.is_empty() {
|
||||
if let Some(secret) = store.get_secret(&config.id, SSH_PASSWORD_KEY)? {
|
||||
config.ssh_password = secret;
|
||||
}
|
||||
} else {
|
||||
store.set_secret(&config.id, SSH_PASSWORD_KEY, &config.ssh_password)?;
|
||||
needs_rewrite = true;
|
||||
}
|
||||
|
||||
match config
|
||||
.connection_string
|
||||
.as_deref()
|
||||
.filter(|secret| !secret.is_empty())
|
||||
{
|
||||
Some(secret) => {
|
||||
store.set_secret(&config.id, CONNECTION_STRING_KEY, secret)?;
|
||||
needs_rewrite = true;
|
||||
}
|
||||
None => {
|
||||
if let Some(secret) = store.get_secret(&config.id, CONNECTION_STRING_KEY)? {
|
||||
config.connection_string = Some(secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needs_rewrite {
|
||||
write_sanitized_connections(path, &configs)?;
|
||||
}
|
||||
|
||||
Ok(configs)
|
||||
}
|
||||
|
||||
fn delete_removed_connection_secrets(
|
||||
path: &Path,
|
||||
configs: &[ConnectionConfig],
|
||||
store: &impl ConnectionSecretStore,
|
||||
) -> Result<(), String> {
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let previous = match read_connections(path) {
|
||||
Ok(configs) => configs,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
let current_ids: HashSet<&str> = configs.iter().map(|config| config.id.as_str()).collect();
|
||||
for config in previous {
|
||||
if current_ids.contains(config.id.as_str()) {
|
||||
continue;
|
||||
}
|
||||
store.delete_secret(&config.id, MAIN_PASSWORD_KEY)?;
|
||||
store.delete_secret(&config.id, SSH_PASSWORD_KEY)?;
|
||||
store.delete_secret(&config.id, CONNECTION_STRING_KEY)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn persist_secret(
|
||||
store: &impl ConnectionSecretStore,
|
||||
connection_id: &str,
|
||||
key: &str,
|
||||
secret: &str,
|
||||
) -> Result<(), String> {
|
||||
if secret.is_empty() {
|
||||
store.delete_secret(connection_id, key)
|
||||
} else {
|
||||
store.set_secret(connection_id, key, secret)
|
||||
}
|
||||
}
|
||||
|
||||
fn persist_optional_secret(
|
||||
store: &impl ConnectionSecretStore,
|
||||
connection_id: &str,
|
||||
key: &str,
|
||||
secret: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
match secret.filter(|secret| !secret.is_empty()) {
|
||||
Some(secret) => store.set_secret(connection_id, key, secret),
|
||||
None => store.delete_secret(connection_id, key),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_connections(path: &Path) -> Result<Vec<ConnectionConfig>, String> {
|
||||
let json = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
serde_json::from_str(&json).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_sanitized_connections(path: &Path, configs: &[ConnectionConfig]) -> Result<(), String> {
|
||||
let sanitized = sanitize_connections(configs);
|
||||
let json = serde_json::to_string_pretty(&sanitized).map_err(|e| e.to_string())?;
|
||||
std::fs::write(path, json).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn sanitize_connections(configs: &[ConnectionConfig]) -> Vec<ConnectionConfig> {
|
||||
configs
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut config| {
|
||||
config.password.clear();
|
||||
config.ssh_password.clear();
|
||||
config.connection_string = None;
|
||||
config
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn keyring_entry(connection_id: &str, key: &str, action: &str) -> Result<keyring::Entry, String> {
|
||||
keyring::Entry::new(KEYRING_SERVICE, &secret_account(connection_id, key))
|
||||
.map_err(|err| keyring_error(action, connection_id, key, err))
|
||||
}
|
||||
|
||||
fn secret_account(connection_id: &str, key: &str) -> String {
|
||||
format!("connection:{connection_id}:{key}")
|
||||
}
|
||||
|
||||
fn keyring_error(action: &str, connection_id: &str, key: &str, err: keyring::Error) -> String {
|
||||
format!("Failed to {action} saved {key} for connection {connection_id}: {err}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
load_connections_from_file, save_connections_to_file, ConnectionSecretStore,
|
||||
CONNECTION_STRING_KEY, MAIN_PASSWORD_KEY, SSH_PASSWORD_KEY,
|
||||
};
|
||||
use crate::models::connection::{ConnectionConfig, DatabaseType};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
struct MemorySecretStore {
|
||||
values: RefCell<HashMap<String, String>>,
|
||||
deleted: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl MemorySecretStore {
|
||||
fn set_existing(&self, connection_id: &str, key: &str, value: &str) {
|
||||
self.values
|
||||
.borrow_mut()
|
||||
.insert(secret_key(connection_id, key), value.to_string());
|
||||
}
|
||||
|
||||
fn get_existing(&self, connection_id: &str, key: &str) -> Option<String> {
|
||||
self.values
|
||||
.borrow()
|
||||
.get(&secret_key(connection_id, key))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn was_deleted(&self, connection_id: &str, key: &str) -> bool {
|
||||
self.deleted
|
||||
.borrow()
|
||||
.contains(&secret_key(connection_id, key))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionSecretStore for MemorySecretStore {
|
||||
fn set_secret(&self, connection_id: &str, key: &str, secret: &str) -> Result<(), String> {
|
||||
self.values
|
||||
.borrow_mut()
|
||||
.insert(secret_key(connection_id, key), secret.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_secret(&self, connection_id: &str, key: &str) -> Result<Option<String>, String> {
|
||||
Ok(self
|
||||
.values
|
||||
.borrow()
|
||||
.get(&secret_key(connection_id, key))
|
||||
.cloned())
|
||||
}
|
||||
|
||||
fn delete_secret(&self, connection_id: &str, key: &str) -> Result<(), String> {
|
||||
self.values
|
||||
.borrow_mut()
|
||||
.remove(&secret_key(connection_id, key));
|
||||
self.deleted
|
||||
.borrow_mut()
|
||||
.push(secret_key(connection_id, key));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn secret_key(connection_id: &str, key: &str) -> String {
|
||||
format!("{connection_id}:{key}")
|
||||
}
|
||||
|
||||
fn temp_connections_file(name: &str) -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"dbx-connection-secrets-test-{}-{name}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir.join("connections.json")
|
||||
}
|
||||
|
||||
fn connection(id: &str, password: &str, ssh_password: &str) -> ConnectionConfig {
|
||||
ConnectionConfig {
|
||||
id: id.to_string(),
|
||||
name: format!("{id} connection"),
|
||||
db_type: DatabaseType::Postgres,
|
||||
driver_profile: None,
|
||||
driver_label: None,
|
||||
url_params: None,
|
||||
host: "localhost".to_string(),
|
||||
port: 5432,
|
||||
username: "postgres".to_string(),
|
||||
password: password.to_string(),
|
||||
database: Some("postgres".to_string()),
|
||||
color: None,
|
||||
ssh_enabled: !ssh_password.is_empty(),
|
||||
ssh_host: String::new(),
|
||||
ssh_port: 22,
|
||||
ssh_user: String::new(),
|
||||
ssh_password: ssh_password.to_string(),
|
||||
ssh_key_path: String::new(),
|
||||
ssh_expose_lan: false,
|
||||
ssl: false,
|
||||
connection_string: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_configs(path: &Path) -> Vec<ConnectionConfig> {
|
||||
let json = std::fs::read_to_string(path).unwrap();
|
||||
serde_json::from_str(&json).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_connections_moves_passwords_to_secret_store_and_redacts_file() {
|
||||
let path = temp_connections_file("save-redacts");
|
||||
let store = MemorySecretStore::default();
|
||||
let configs = vec![connection("main", "db-secret", "ssh-secret")];
|
||||
|
||||
save_connections_to_file(&path, &configs, &store).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store.get_existing("main", MAIN_PASSWORD_KEY).as_deref(),
|
||||
Some("db-secret")
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_existing("main", SSH_PASSWORD_KEY).as_deref(),
|
||||
Some("ssh-secret")
|
||||
);
|
||||
let persisted = read_configs(&path);
|
||||
assert_eq!(persisted[0].password, "");
|
||||
assert_eq!(persisted[0].ssh_password, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_connections_restores_passwords_from_secret_store() {
|
||||
let path = temp_connections_file("load-restores");
|
||||
let store = MemorySecretStore::default();
|
||||
store.set_existing("main", MAIN_PASSWORD_KEY, "db-secret");
|
||||
store.set_existing("main", SSH_PASSWORD_KEY, "ssh-secret");
|
||||
let sanitized = vec![connection("main", "", "")];
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&sanitized).unwrap()).unwrap();
|
||||
|
||||
let loaded = load_connections_from_file(&path, &store).unwrap();
|
||||
|
||||
assert_eq!(loaded[0].password, "db-secret");
|
||||
assert_eq!(loaded[0].ssh_password, "ssh-secret");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_connections_migrates_plaintext_passwords_and_rewrites_sanitized_file() {
|
||||
let path = temp_connections_file("migrates-plaintext");
|
||||
let store = MemorySecretStore::default();
|
||||
let legacy = vec![connection("legacy", "plain-db", "plain-ssh")];
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&legacy).unwrap()).unwrap();
|
||||
|
||||
let loaded = load_connections_from_file(&path, &store).unwrap();
|
||||
|
||||
assert_eq!(loaded[0].password, "plain-db");
|
||||
assert_eq!(loaded[0].ssh_password, "plain-ssh");
|
||||
assert_eq!(
|
||||
store.get_existing("legacy", MAIN_PASSWORD_KEY).as_deref(),
|
||||
Some("plain-db")
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_existing("legacy", SSH_PASSWORD_KEY).as_deref(),
|
||||
Some("plain-ssh")
|
||||
);
|
||||
let persisted = read_configs(&path);
|
||||
assert_eq!(persisted[0].password, "");
|
||||
assert_eq!(persisted[0].ssh_password, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_connections_deletes_secrets_for_removed_connections() {
|
||||
let path = temp_connections_file("deletes-removed");
|
||||
let store = MemorySecretStore::default();
|
||||
let previous = vec![connection("old", "", ""), connection("kept", "", "")];
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&previous).unwrap()).unwrap();
|
||||
store.set_existing("old", MAIN_PASSWORD_KEY, "old-db");
|
||||
store.set_existing("old", SSH_PASSWORD_KEY, "old-ssh");
|
||||
store.set_existing("kept", MAIN_PASSWORD_KEY, "kept-db");
|
||||
|
||||
save_connections_to_file(&path, &[connection("kept", "new-db", "")], &store).unwrap();
|
||||
|
||||
assert!(store.was_deleted("old", MAIN_PASSWORD_KEY));
|
||||
assert!(store.was_deleted("old", SSH_PASSWORD_KEY));
|
||||
assert_eq!(
|
||||
store.get_existing("kept", MAIN_PASSWORD_KEY).as_deref(),
|
||||
Some("new-db")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_connections_moves_connection_string_to_secret_store_and_restores_it() {
|
||||
let path = temp_connections_file("connection-string");
|
||||
let store = MemorySecretStore::default();
|
||||
let mut config = connection("mongo", "", "");
|
||||
config.db_type = DatabaseType::MongoDb;
|
||||
config.connection_string = Some("mongodb://user:secret@localhost/app".to_string());
|
||||
|
||||
save_connections_to_file(&path, &[config], &store).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store
|
||||
.get_existing("mongo", CONNECTION_STRING_KEY)
|
||||
.as_deref(),
|
||||
Some("mongodb://user:secret@localhost/app")
|
||||
);
|
||||
let persisted = read_configs(&path);
|
||||
assert_eq!(persisted[0].connection_string, None);
|
||||
|
||||
let loaded = load_connections_from_file(&path, &store).unwrap();
|
||||
assert_eq!(
|
||||
loaded[0].connection_string.as_deref(),
|
||||
Some("mongodb://user:secret@localhost/app")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod ai;
|
||||
pub mod connection;
|
||||
mod connection_secrets;
|
||||
pub mod history;
|
||||
pub mod mongo_cmd;
|
||||
pub mod query;
|
||||
|
|
|
|||
|
|
@ -249,6 +249,7 @@ mod tests {
|
|||
ssh_user: String::new(),
|
||||
ssh_password: String::new(),
|
||||
ssh_key_path: String::new(),
|
||||
ssh_expose_lan: false,
|
||||
ssl: false,
|
||||
connection_string: None,
|
||||
}
|
||||
|
|
|
|||
14
src/App.vue
14
src/App.vue
|
|
@ -596,7 +596,9 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
|
||||
onMounted(() => {
|
||||
applyTheme();
|
||||
connectionStore.initFromDisk();
|
||||
connectionStore.initFromDisk().catch((e: any) => {
|
||||
toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000);
|
||||
});
|
||||
settingsStore.initAiConfig();
|
||||
window.addEventListener("keydown", handleKeydown, true);
|
||||
setupFileDrop();
|
||||
|
|
@ -671,9 +673,13 @@ async function setupFileDrop() {
|
|||
username: "",
|
||||
password: "",
|
||||
};
|
||||
connectionStore.addConnection(config);
|
||||
connectionStore.connect(config);
|
||||
toast(t("welcome.fileOpened", { name }));
|
||||
try {
|
||||
await connectionStore.addConnection(config);
|
||||
void connectionStore.connect(config);
|
||||
toast(t("welcome.fileOpened", { name }));
|
||||
} catch (e: any) {
|
||||
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,11 +269,11 @@ async function save() {
|
|||
try {
|
||||
if (editingId.value) {
|
||||
const updated: ConnectionConfig = { ...form.value, id: editingId.value };
|
||||
store.updateConnection(updated);
|
||||
await store.updateConnection(updated);
|
||||
store.stopEditing();
|
||||
} else {
|
||||
const config: ConnectionConfig = { ...form.value, id: crypto.randomUUID() };
|
||||
store.addConnection(config);
|
||||
await store.addConnection(config);
|
||||
open.value = false;
|
||||
await nextTick();
|
||||
emit("connectStarted", config.name);
|
||||
|
|
|
|||
|
|
@ -220,11 +220,15 @@ function deleteConnection() {
|
|||
showDeleteConfirm.value = true;
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
async function confirmDelete() {
|
||||
const node = props.node;
|
||||
if (node.connectionId) {
|
||||
connectionStore.disconnect(node.connectionId);
|
||||
connectionStore.removeConnection(node.connectionId);
|
||||
try {
|
||||
await connectionStore.disconnect(node.connectionId);
|
||||
await connectionStore.removeConnection(node.connectionId);
|
||||
} catch (e: any) {
|
||||
toast(t("connection.saveFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export default {
|
|||
connecting: "Connecting to {name}...",
|
||||
connectSuccess: "Connected to {name}",
|
||||
connectFailed: "Connection failed: {message}",
|
||||
saveFailed: "Failed to save connection: {message}",
|
||||
loadFailed: "Failed to load saved connections: {message}",
|
||||
sshTunnel: "SSH Tunnel",
|
||||
sshEnable: "Connect via SSH tunnel",
|
||||
sshHost: "SSH Host",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ export default {
|
|||
connecting: "正在连接 {name}...",
|
||||
connectSuccess: "已连接 {name}",
|
||||
connectFailed: "连接失败:{message}",
|
||||
saveFailed: "保存连接失败:{message}",
|
||||
loadFailed: "加载已保存连接失败:{message}",
|
||||
sshTunnel: "SSH 隧道",
|
||||
sshEnable: "通过 SSH 隧道连接",
|
||||
sshHost: "SSH 主机",
|
||||
|
|
|
|||
|
|
@ -130,16 +130,18 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
}
|
||||
}
|
||||
|
||||
function addConnection(config: ConnectionConfig) {
|
||||
async function addConnection(config: ConnectionConfig) {
|
||||
const normalized = normalizeConnection(config);
|
||||
const existing = connections.value.findIndex((c) => c.id === normalized.id);
|
||||
const nextConnections = [...connections.value];
|
||||
if (existing >= 0) {
|
||||
connections.value[existing] = normalized;
|
||||
nextConnections[existing] = normalized;
|
||||
} else {
|
||||
connections.value.push(normalized);
|
||||
nextConnections.push(normalized);
|
||||
}
|
||||
await persistConnections(nextConnections);
|
||||
connections.value = nextConnections;
|
||||
upsertConnectionNode(normalized);
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
function invalidateCompletionCache(connectionId: string) {
|
||||
|
|
@ -152,20 +154,25 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
);
|
||||
}
|
||||
|
||||
function removeConnection(id: string) {
|
||||
connections.value = connections.value.filter((c) => c.id !== id);
|
||||
async function removeConnection(id: string) {
|
||||
const nextConnections = connections.value.filter((c) => c.id !== id);
|
||||
await persistConnections(nextConnections);
|
||||
connections.value = nextConnections;
|
||||
treeNodes.value = treeNodes.value.filter((n) => n.id !== id);
|
||||
if (activeConnectionId.value === id) {
|
||||
activeConnectionId.value = null;
|
||||
}
|
||||
invalidateCompletionCache(id);
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
function updateConnection(config: ConnectionConfig) {
|
||||
async function updateConnection(config: ConnectionConfig) {
|
||||
config = normalizeConnection(config);
|
||||
const idx = connections.value.findIndex((c) => c.id === config.id);
|
||||
if (idx >= 0) connections.value[idx] = config;
|
||||
if (idx < 0) return;
|
||||
const nextConnections = [...connections.value];
|
||||
nextConnections[idx] = config;
|
||||
await persistConnections(nextConnections);
|
||||
connections.value = nextConnections;
|
||||
const node = findNode(treeNodes.value, config.id);
|
||||
if (node) {
|
||||
node.label = config.name;
|
||||
|
|
@ -174,7 +181,6 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
}
|
||||
connectedIds.value.delete(config.id);
|
||||
invalidateCompletionCache(config.id);
|
||||
persistConnections();
|
||||
}
|
||||
|
||||
async function connect(config: ConnectionConfig) {
|
||||
|
|
@ -561,8 +567,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
function persistConnections() {
|
||||
api.saveConnections(connections.value).catch(() => {});
|
||||
async function persistConnections(nextConnections: ConnectionConfig[] = connections.value) {
|
||||
await api.saveConnections(nextConnections);
|
||||
}
|
||||
|
||||
async function exportConnectionsToFile() {
|
||||
|
|
@ -570,7 +576,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
const { writeTextFile } = await import("@tauri-apps/plugin-fs");
|
||||
const path = await save({ filters: [{ name: "JSON", extensions: ["json"] }], defaultPath: "dbx-connections.json" });
|
||||
if (!path) return;
|
||||
const data = connections.value.map((c) => ({ ...c, password: "", ssh_password: "" }));
|
||||
const data = connections.value.map((c) => ({ ...c, password: "", ssh_password: "", connection_string: undefined }));
|
||||
await writeTextFile(path, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
|
|
@ -585,7 +591,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
if (!connections.value.find((c) => c.id === config.id)) {
|
||||
config.id = crypto.randomUUID();
|
||||
const normalized = normalizeConnection(config);
|
||||
addConnection(normalized);
|
||||
await addConnection(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue