diff --git a/.github/scripts/database-issue-catalog.mjs b/.github/scripts/database-issue-catalog.mjs index 402b4c1c4c..350d17d7ce 100644 --- a/.github/scripts/database-issue-catalog.mjs +++ b/.github/scripts/database-issue-catalog.mjs @@ -84,6 +84,7 @@ const supplementalDrivers = [ { dbType: "kafka", label: "Apache Kafka" }, { dbType: "rocketmq", label: "Apache RocketMQ" }, { dbType: "rabbitmq", label: "RabbitMQ" }, + { dbType: "mqtt", label: "MQTT", aliases: ["emqx", "hivemq", "mosquitto"] }, ]; const manifestUrl = new URL("../../crates/dbx-core/assets/database-drivers.manifest.json", import.meta.url); diff --git a/.github/scripts/label-issue-database.test.mjs b/.github/scripts/label-issue-database.test.mjs index 74f48a2d16..782eccf7c1 100644 --- a/.github/scripts/label-issue-database.test.mjs +++ b/.github/scripts/label-issue-database.test.mjs @@ -57,6 +57,8 @@ test("labels native and compatibility database products with their families", () ["Dremio", ["db/dremio", "db/jdbc"]], ["Apache Kafka", ["db/kafka", "db/mq"]], ["RabbitMQ", ["db/mq", "db/rabbitmq"]], + ["MQTT 5.0", ["db/mqtt"]], + ["EMQX 5.8", ["db/mqtt"]], ]; for (const [database, expected] of cases) { diff --git a/Cargo.lock b/Cargo.lock index 36dc614c3f..14987fa8de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher 0.5.2", "cpubits", @@ -71,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" dependencies = [ "aead 0.6.0-rc.10", - "aes 0.9.1", + "aes 0.9.2", "cipher 0.5.2", "ctr 0.10.1", "ghash 0.6.0", @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" dependencies = [ "compression-codecs", "compression-core", @@ -395,6 +395,35 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite 0.21.0", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", + "tokio", +] + [[package]] name = "asynchronous-codec" version = "0.6.2" @@ -897,9 +926,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" dependencies = [ "serde_core", ] @@ -1646,7 +1675,7 @@ dependencies = [ "reqwest 0.12.28", "russh", "rust_decimal", - "rustls 0.23.42", + "rustls 0.23.43", "semver", "serde", "serde_json", @@ -1726,10 +1755,11 @@ dependencies = [ "redis", "regex", "reqwest 0.12.28", + "rumqttc", "rusqlite", "russh", "rust_decimal", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-pemfile 2.2.0", "serde", "serde_json", @@ -1767,7 +1797,7 @@ dependencies = [ "dirs", "reqwest 0.12.28", "rmcp", - "schemars 1.2.1", + "schemars 1.2.2", "serde", "serde_json", "tempfile", @@ -1792,7 +1822,7 @@ dependencies = [ "pbkdf2 0.12.2", "redis", "reqwest 0.12.28", - "rustls 0.23.42", + "rustls 0.23.43", "serde", "serde_json", "sha2 0.10.9", @@ -2021,13 +2051,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -2234,7 +2264,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "vswhom", "winreg", ] @@ -2344,11 +2374,10 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -2484,6 +2513,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -3280,9 +3320,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -3372,7 +3412,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.42", + "rustls 0.23.43", "tokio", "tokio-rustls 0.26.4", "tower-service", @@ -4442,7 +4482,7 @@ dependencies = [ "percent-encoding", "rand 0.9.5", "rustc_version_runtime", - "rustls 0.23.42", + "rustls 0.23.43", "serde", "serde_bytes", "serde_with", @@ -4555,7 +4595,7 @@ dependencies = [ "mysql_common", "percent-encoding", "rand 0.10.2", - "rustls 0.23.42", + "rustls 0.23.43", "serde", "socket2", "thiserror 2.0.19", @@ -5438,6 +5478,16 @@ dependencies = [ "indexmap 2.14.0", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + [[package]] name = "phf" version = "0.13.1" @@ -5524,7 +5574,7 @@ version = "0.8.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5a777c6e26664bc9504b3ce3f6133f8f20d9071f130a4f9fcbd3186959d8dd6" dependencies = [ - "aes 0.9.1", + "aes 0.9.2", "aes-gcm 0.11.0-rc.3", "cbc 0.2.1", "der 0.8.1", @@ -5922,7 +5972,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.42", + "rustls 0.23.43", "socket2", "thiserror 2.0.19", "tokio", @@ -5943,7 +5993,7 @@ dependencies = [ "rand_pcg", "ring", "rustc-hash", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-pki-types", "slab", "thiserror 2.0.19", @@ -6142,7 +6192,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand 0.9.5", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-native-certs 0.8.4", "ryu", "sha1_smol", @@ -6257,7 +6307,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-pki-types", "serde", "serde_json", @@ -6298,7 +6348,7 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -6413,7 +6463,7 @@ dependencies = [ "pastey", "pin-project-lite", "rmcp-macros", - "schemars 1.2.1", + "schemars 1.2.2", "serde", "serde_json", "thiserror 2.0.19", @@ -6455,6 +6505,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rumqttc" +version = "0.24.0" +dependencies = [ + "async-tungstenite", + "bytes", + "flume", + "futures-util", + "http", + "log", + "rustls-native-certs 0.7.3", + "rustls-pemfile 2.2.0", + "rustls-webpki 0.102.8", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.25.0", + "ws_stream_tungstenite", +] + [[package]] name = "rusqlite" version = "0.32.1" @@ -6477,7 +6546,7 @@ checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" dependencies = [ "aead 0.6.0-rc.10", "aes 0.8.4", - "aes 0.9.1", + "aes 0.9.2", "aes-gcm 0.11.0-rc.3", "aws-lc-rs", "bitflags 2.13.1", @@ -6676,9 +6745,23 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -6702,6 +6785,19 @@ dependencies = [ "security-framework 2.11.1", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe 0.1.6", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + [[package]] name = "rustls-native-certs" version = "0.8.4" @@ -6753,7 +6849,7 @@ dependencies = [ "jni 0.22.4", "log", "once_cell", - "rustls 0.23.42", + "rustls 0.23.43", "rustls-native-certs 0.8.4", "rustls-platform-verifier-android", "rustls-webpki 0.103.13", @@ -6779,6 +6875,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -6866,14 +6973,14 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" dependencies = [ "chrono", "dyn-clone", "ref-cast", - "schemars_derive 1.2.1", + "schemars_derive 1.2.2", "serde", "serde_json", ] @@ -6886,20 +6993,20 @@ checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", + "serde_derive_internals 0.29.1", "syn 2.0.119", ] [[package]] name = "schemars_derive" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", - "syn 2.0.119", + "serde_derive_internals 0.30.0", + "syn 3.0.3", ] [[package]] @@ -7078,6 +7185,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "serde_json" version = "1.0.151" @@ -7157,7 +7275,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.14.0", "schemars 0.9.0", - "schemars 1.2.1", + "schemars 1.2.2", "serde_core", "serde_json", "serde_with_macros", @@ -7491,6 +7609,9 @@ name = "spin" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -7814,9 +7935,9 @@ dependencies = [ [[package]] name = "tao-macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" dependencies = [ "proc-macro2", "quote", @@ -8049,7 +8170,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.19", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "url", ] @@ -8138,7 +8259,7 @@ dependencies = [ "osakit", "percent-encoding", "reqwest 0.13.4", - "rustls 0.23.42", + "rustls 0.23.43", "semver", "serde", "serde_json", @@ -8251,7 +8372,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.19", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", "url", "urlpattern", "uuid", @@ -8266,7 +8387,7 @@ checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 1.1.3+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", ] [[package]] @@ -8499,13 +8620,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -8551,7 +8672,7 @@ checksum = "27d684bad428a0f2481f42241f821db42c54e2dc81d8c00db8536c506b0a0144" dependencies = [ "const-oid 0.9.6", "ring", - "rustls 0.23.42", + "rustls 0.23.43", "tokio", "tokio-postgres", "tokio-rustls 0.26.4", @@ -8568,13 +8689,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.42", + "rustls 0.23.43", "tokio", ] @@ -8598,7 +8730,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.29.0", ] [[package]] @@ -8646,9 +8778,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.3+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap 2.14.0", "serde_core", @@ -8724,9 +8856,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow 1.0.4", ] @@ -8858,9 +8990,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" dependencies = [ "crossbeam-channel", "dirs", @@ -8895,6 +9027,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.7", + "rustls 0.22.4", + "rustls-pki-types", + "sha1 0.10.7", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.29.0" @@ -9121,6 +9274,12 @@ dependencies = [ "url", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -10252,6 +10411,26 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "ws_stream_tungstenite" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a198f414f083fb19fcc1bffcb0fa0cf46d33ccfa229adf248cac12c180e91609" +dependencies = [ + "async-tungstenite", + "async_io_stream", + "bitflags 2.13.1", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "pharos", + "rustc_version", + "tokio", + "tracing", + "tungstenite 0.21.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index d6672554f0..0b37b46816 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,14 @@ [workspace] resolver = "2" members = ["src-tauri", "crates/dbx-core", "crates/dbx-web", "crates/dbx-mcp", "crates/dbx-cli"] -exclude = ["vendor/ctor"] +exclude = ["vendor/ctor", "vendor/rumqttc"] [patch.crates-io] # Tauri 2.11 uses ctor 0.8, which excludes Rust's win7 vendor. This vendors # upstream rust-ctor#443 until tauri-utils moves to ctor 1.x. ctor = { path = "vendor/ctor" } +# rumqttc 0.24 hardcodes MQTT 3.1.1; this patch adds MQTT 3.1 CONNECT encoding. +rumqttc = { path = "vendor/rumqttc" } tokio-postgres = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } postgres-types = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } postgres-protocol = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", rev = "115f9fef10f0fc3669b5337955e4eb461fc349a6" } diff --git a/apps/desktop/public/icons/database/mqtt.png b/apps/desktop/public/icons/database/mqtt.png new file mode 100644 index 0000000000..28b7223e14 Binary files /dev/null and b/apps/desktop/public/icons/database/mqtt.png differ diff --git a/apps/desktop/public/icons/database/mqtt.svg b/apps/desktop/public/icons/database/mqtt.svg new file mode 100644 index 0000000000..237b47c7d4 --- /dev/null +++ b/apps/desktop/public/icons/database/mqtt.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 684d18acf4..526461d358 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -17,6 +17,7 @@ import { Switch } from "@/components/ui/switch"; import type { ConnectionConfig, ConnectionTestResult, DatabaseConnectionInfo, DatabaseType, HttpTunnelConfig, IdentifierCase, JdbcDriverInfo, JdbcLocalBundleInfo, JdbcMavenBundleInfo, ProxyTunnelConfig, SshConfigHostEntry, SshTunnelConfig, TransportLayerConfig } from "@/types/database"; import type { InfluxDbExternalConfig, InfluxDbVersion } from "@/types/influxdb"; import type { MqAdminConfig, MqAuth, MqSystemKind } from "@/types/mq"; +import type { MqttConnectionConfig } from "@/types/mqtt"; import type { NacosAdminConfig, NacosAuthConfig, NacosImplementation, NacosMetricsMode, NacosRNacosConsoleAuth, NacosVersionMode } from "@/types/nacos"; import { CONNECTION_ATTEMPT_CANCELLED_MESSAGE, useConnectionStore } from "@/stores/connectionStore"; import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; @@ -657,6 +658,23 @@ const nacosTlsSkipVerify = ref(false); const nacosMetricsMode = ref("auto"); const nacosMetricsUrl = ref(""); const nacosPageSize = ref(20); + +// --- MQTT-specific form fields --- +const mqttHost = ref("127.0.0.1"); +const mqttPort = ref(1883); +const mqttClientId = ref(""); +const mqttProtocolVersion = ref<"v3" | "v4" | "v5">("v5"); +const mqttTransportMode = ref<"tcp" | "websocket">("tcp"); +const mqttWsPath = ref("/mqtt"); +const mqttAuthKind = ref<"none" | "password">("none"); +const mqttUsername = ref(""); +const mqttPassword = ref(""); +const mqttTls = ref(false); +const mqttTlsSkipVerify = ref(false); +const mqttKeepAliveSecs = ref(60); +const mqttConnectTimeoutSecs = ref(30); +const mqttMaxPacketSizeBytes = ref(16 * 1024 * 1024); + const nacosPrimaryAddressLabel = computed(() => { if (nacosImplementation.value === "rnacos") return t("connection.nacosPrimaryAddressRNacos"); if (nacosVersionMode.value === "v2") return t("connection.nacosPrimaryAddressV2"); @@ -973,6 +991,7 @@ const driverProfiles: Record< rocketmq: { type: "mq", port: 9876, user: "", label: "Apache RocketMQ", icon: "rocketmq", host: "127.0.0.1" }, rabbitmq: { type: "mq", port: 5672, user: "", label: "RabbitMQ", icon: "rabbitmq", host: "127.0.0.1" }, nacos: { type: "nacos", port: 8848, user: "nacos", label: "Nacos", icon: "nacos", host: "127.0.0.1" }, + mqtt: { type: "mqtt", port: 1883, user: "", label: "MQTT", icon: "mqtt", host: "127.0.0.1" }, iris: { type: "iris", port: 1972, user: "_SYSTEM", label: "IRIS", icon: "iris" }, influxdb: { type: "influxdb", port: 8086, user: "", label: "InfluxDB", icon: "InfluxDB" }, custom_mysql: { @@ -1188,6 +1207,57 @@ function hydrateNacosFields(value: unknown) { resetNacosFields(value as Partial); } +function resetMqttFields(config?: Partial) { + mqttHost.value = config?.host?.trim() || "127.0.0.1"; + mqttPort.value = config?.port || 1883; + mqttClientId.value = config?.clientId || ""; + mqttProtocolVersion.value = config?.protocolVersion || "v5"; + mqttTransportMode.value = config?.transport || "tcp"; + mqttWsPath.value = config?.wsPath || "/mqtt"; + mqttTls.value = config?.tls || false; + mqttTlsSkipVerify.value = config?.tlsSkipVerify || false; + mqttKeepAliveSecs.value = Math.max(1, config?.keepAliveSecs || 60); + mqttConnectTimeoutSecs.value = Math.max(1, config?.connectTimeoutSecs || 30); + mqttMaxPacketSizeBytes.value = Math.min(268435455, Math.max(1024, config?.maxPacketSizeBytes || 16 * 1024 * 1024)); + const auth = config?.auth; + if (auth && auth.kind === "password") { + mqttAuthKind.value = "password"; + mqttUsername.value = auth.username || ""; + mqttPassword.value = auth.password || ""; + } else { + mqttAuthKind.value = "none"; + mqttUsername.value = ""; + mqttPassword.value = ""; + } +} + +function hydrateMqttFields(value: unknown) { + if (!value || typeof value !== "object") { + resetMqttFields(); + return; + } + resetMqttFields(value as Partial); +} + +function buildMqttExternalConfig(): MqttConnectionConfig { + const auth: MqttConnectionConfig["auth"] = mqttAuthKind.value === "password" ? { kind: "password", username: mqttUsername.value, password: mqttPassword.value } : { kind: "none" }; + + return { + host: mqttHost.value.trim(), + port: mqttPort.value, + clientId: mqttClientId.value.trim() || `dbx-${Math.random().toString(36).slice(2, 10)}`, + protocolVersion: mqttProtocolVersion.value, + transport: mqttTransportMode.value, + tls: mqttTls.value, + tlsSkipVerify: mqttTlsSkipVerify.value, + auth, + keepAliveSecs: Math.max(1, mqttKeepAliveSecs.value), + connectTimeoutSecs: Math.max(1, mqttConnectTimeoutSecs.value), + maxPacketSizeBytes: Math.min(268435455, Math.max(1024, mqttMaxPacketSizeBytes.value)), + wsPath: mqttTransportMode.value === "websocket" ? mqttWsPath.value || "/mqtt" : undefined, + }; +} + const influxDbVersion = ref("1"); const influxDbOrg = ref(""); @@ -1957,6 +2027,12 @@ function applyProfile(val: string, preserveConnectionFields = false) { form.value.connection_string = undefined; form.value.url_params = ""; } + if (profile.type === "mqtt") { + resetMqttFields(); + form.value.database = undefined; + form.value.connection_string = undefined; + form.value.url_params = ""; + } if (profile.type === "influxdb") { resetInfluxDbFields(); form.value.database = undefined; @@ -2060,6 +2136,11 @@ watch( } else { resetNacosFields(); } + if (config.db_type === "mqtt") { + hydrateMqttFields(config.external_config); + } else { + resetMqttFields(); + } if (config.db_type === "influxdb") { hydrateInfluxDbFields(config.external_config); } else { @@ -2304,6 +2385,7 @@ const iconTypeMap: Record = { rocketmq: "rocketmq", rabbitmq: "rabbitmq", nacos: "nacos", + mqtt: "mqtt", dm: "dm", h2: "h2", snowflake: "snowflake", @@ -2403,6 +2485,7 @@ const dbOptions: DbOption[] = [ { value: "kafka", label: "Apache Kafka" }, { value: "rocketmq", label: "Apache RocketMQ" }, { value: "rabbitmq", label: "RabbitMQ" }, + { value: "mqtt", label: "MQTT" }, { value: "nacos", label: "Nacos" }, { value: "influxdb", label: "InfluxDB" }, { value: "iris", label: "IRIS" }, @@ -2456,7 +2539,7 @@ const dbCategoryDefinitions: Array<{ { key: "mq", titleKey: "connection.databaseCategoryMq", - optionValues: ["mq", "kafka", "rocketmq", "rabbitmq"], + optionValues: ["mq", "kafka", "rocketmq", "rabbitmq", "mqtt"], }, { key: "registry_config", @@ -2864,6 +2947,7 @@ const hasRequiredConnectionTarget = computed(() => { return !!mqAdminUrl.value.trim(); } if (form.value.db_type === "zookeeper") return !!(form.value.host || form.value.connection_string || connectionUrlInput.value.trim()); + if (form.value.db_type === "mqtt") return !!mqttHost.value.trim() && mqttPort.value > 0; if (form.value.db_type === "nacos") return !!nacosServerAddr.value.trim(); if (isCloudflareD1Connection(form.value)) return hasCloudflareD1Credentials(form.value); if (isH2FileMode.value) return !!(form.value.host.trim() || h2FilePathFromJdbcUrl(form.value.connection_string)); @@ -3264,6 +3348,19 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo config.database = nacosConfig.namespace || undefined; config.connection_string = undefined; config.url_params = ""; + } else if (config.db_type === "mqtt") { + const mqttConfig = buildMqttExternalConfig(); + config.external_config = mqttConfig; + config.driver_profile = "mqtt"; + config.driver_label = "MQTT"; + config.host = mqttConfig.host; + config.port = mqttConfig.port; + config.ssl = mqttConfig.tls; + config.username = ""; + config.password = ""; + config.database = undefined; + config.connection_string = undefined; + config.url_params = ""; } else if (config.db_type === "influxdb") { config.external_config = buildInfluxDbExternalConfig(); config.connection_string = undefined; @@ -5851,6 +5948,81 @@ function openExternalUrl(url: string) { + + + + +