diff --git a/Cargo.lock b/Cargo.lock index df1b0fb130..c18cf4c15a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2057,6 +2057,7 @@ dependencies = [ "csv", "dbx-core", "dbx-mcp", + "dbx-tauri-consul", "deadpool-postgres", "font-kit", "futures", @@ -2609,6 +2610,16 @@ dependencies = [ "sqlparser", ] +[[package]] +name = "dbx-tauri-consul" +version = "0.1.0" +dependencies = [ + "dbx-core", + "serde", + "serde_json", + "tauri", +] + [[package]] name = "dbx-types" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index fa0a54ef80..4b25f4fd72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["src-tauri", "crates/dbx-core", "crates/dbx-driver-agent", "crates/dbx-driver-support", "crates/dbx-driver-elasticsearch", "crates/dbx-driver-mongodb", "crates/dbx-driver-mysql", "crates/dbx-driver-postgres", "crates/dbx-driver-redis", "crates/dbx-driver-sqlserver", "crates/dbx-drivers", "crates/dbx-sql", "crates/dbx-sql-core", "crates/dbx-sql-data", "crates/dbx-sql-dialect", "crates/dbx-sql-schema", "crates/dbx-plugin-runtime", "crates/dbx-formats", "crates/dbx-types", "crates/dbx-platform", "crates/dbx-ai-provider", "crates/dbx-web", "crates/dbx-mcp", "crates/dbx-cli", "crates/dbx-sqlite-worker"] +members = ["src-tauri", "crates/dbx-core", "crates/dbx-driver-agent", "crates/dbx-driver-support", "crates/dbx-driver-elasticsearch", "crates/dbx-driver-mongodb", "crates/dbx-driver-mysql", "crates/dbx-driver-postgres", "crates/dbx-driver-redis", "crates/dbx-driver-sqlserver", "crates/dbx-drivers", "crates/dbx-sql", "crates/dbx-sql-core", "crates/dbx-sql-data", "crates/dbx-sql-dialect", "crates/dbx-sql-schema", "crates/dbx-plugin-runtime", "crates/dbx-formats", "crates/dbx-types", "crates/dbx-platform", "crates/dbx-ai-provider", "crates/dbx-web", "crates/dbx-mcp", "crates/dbx-cli", "crates/dbx-sqlite-worker", "crates/dbx-tauri-consul"] exclude = ["vendor/tauri-plugin-updater", "vendor/ctor", "vendor/dirs-sys", "vendor/pageant", "vendor/rumqttc", "vendor/tiberius", "vendor/webview2-com-sys", "vendor/wry"] [patch.crates-io] diff --git a/apps/desktop/src/lib/backend/__tests__/consulTransportContract.spec.ts b/apps/desktop/src/lib/backend/__tests__/consulTransportContract.spec.ts index f73644db8a..5df73f762f 100644 --- a/apps/desktop/src/lib/backend/__tests__/consulTransportContract.spec.ts +++ b/apps/desktop/src/lib/backend/__tests__/consulTransportContract.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; const tauri = readFileSync(new URL("../tauri.ts", import.meta.url), "utf8"); const http = readFileSync(new URL("../http.ts", import.meta.url), "utf8"); const api = readFileSync(new URL("../api.ts", import.meta.url), "utf8"); -const tauriRegistry = readFileSync(new URL("../../../../../../src-tauri/src/lib.rs", import.meta.url), "utf8"); +const tauriRegistry = readFileSync(new URL("../../../../../../crates/dbx-tauri-consul/src/lib.rs", import.meta.url), "utf8"); const webRegistry = readFileSync(new URL("../../../../../../crates/dbx-web/src/main.rs", import.meta.url), "utf8"); const operations = [...api.matchAll(/export const (consul\w+) = forward/g)].map((match) => match[1]); const expectedOperations = [ @@ -141,7 +141,7 @@ describe("Consul dual transport contract", () => { const route = httpBody.match(/post\("([^"]+)"/)?.[1] ?? (operation === "consulWatchStart" && httpBody.includes("consulBlockingQuery") ? "/api/consul/blocking-query" : undefined); expect(command, `${operation} invoke command`).toBe(snakeCase(operation)); expect(route, `${operation} HTTP route`).toMatch(/^\/api\/consul\//); - expect(tauriRegistry).toContain(`commands::consul_cmd::${command},`); + expect(tauriRegistry).toContain(`commands::${command},`); expect(webRegistry).toContain(`.route("${route?.replace(/^\/api/, "")}", post(`); }); diff --git a/crates/dbx-tauri-consul/Cargo.toml b/crates/dbx-tauri-consul/Cargo.toml new file mode 100644 index 0000000000..87feb4c593 --- /dev/null +++ b/crates/dbx-tauri-consul/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "dbx-tauri-consul" +version = "0.1.0" +edition = "2021" +rust-version = "1.88.0" + +[dependencies] +dbx-core = { path = "../dbx-core", default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tauri = "2.10.3" diff --git a/src-tauri/src/commands/consul_cmd.rs b/crates/dbx-tauri-consul/src/commands.rs similarity index 98% rename from src-tauri/src/commands/consul_cmd.rs rename to crates/dbx-tauri-consul/src/commands.rs index b010b0386e..015460d688 100644 --- a/src-tauri/src/commands/consul_cmd.rs +++ b/crates/dbx-tauri-consul/src/commands.rs @@ -3,10 +3,20 @@ use std::sync::Arc; use serde::Serialize; use tauri::{AppHandle, Emitter, State}; -use crate::commands::connection::{ensure_connection_writable, AppState}; use dbx_core::agent_kv::{ KvDeleteOptions, KvDeleteResponse, KvGetResponse, KvListPrefixResponse, KvPutOptions, KvPutResponse, KvValue, }; +use dbx_core::connection::AppState; + +async fn ensure_connection_writable(state: &Arc, connection_id: &str, action: &str) -> Result<(), String> { + if let Some(name) = dbx_core::query::connection_readonly_name(state, connection_id).await { + return Err(format!( + "Read-only mode: connection '{}' has read-only protection enabled. {} blocked.", + name, action + )); + } + Ok(()) +} #[tauri::command] pub async fn consul_capabilities( diff --git a/crates/dbx-tauri-consul/src/lib.rs b/crates/dbx-tauri-consul/src/lib.rs new file mode 100644 index 0000000000..71fdf116df --- /dev/null +++ b/crates/dbx-tauri-consul/src/lib.rs @@ -0,0 +1,137 @@ +mod commands; + +pub fn handles(command: &str) -> bool { + command.starts_with("consul_") +} + +pub fn invoke_handler() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static { + tauri::generate_handler![ + commands::consul_capabilities, + commands::consul_txn, + commands::consul_rename_key, + commands::consul_blocking_query, + commands::consul_domain_watch, + commands::consul_cancel_blocking, + commands::consul_watch_start, + commands::consul_list_prefix, + commands::consul_list_recursive, + commands::consul_search, + commands::consul_search_progress, + commands::consul_cancel_search, + commands::consul_export_bundle, + commands::consul_import_preview, + commands::consul_import_execute, + commands::consul_delete_prefix_preview, + commands::consul_delete_prefix_execute, + commands::consul_get, + commands::consul_put, + commands::consul_delete, + commands::consul_prepared_query_list, + commands::consul_prepared_query_read, + commands::consul_prepared_query_create, + commands::consul_prepared_query_update, + commands::consul_prepared_query_delete, + commands::consul_prepared_query_execute, + commands::consul_prepared_query_explain, + commands::consul_event_list, + commands::consul_event_fire, + commands::consul_coordinate_nodes, + commands::consul_operator_read, + commands::consul_snapshot_generate, + commands::consul_snapshot_restore, + commands::consul_autopilot_update, + commands::consul_raft_transfer, + commands::consul_raft_remove, + commands::consul_keyring_write, + commands::consul_license_write, + commands::consul_status_leader, + commands::consul_status_peers, + commands::consul_agent_self, + commands::consul_agent_members, + commands::consul_agent_metrics, + commands::consul_catalog_datacenters, + commands::consul_catalog_nodes, + commands::consul_catalog_services, + commands::consul_catalog_service_nodes, + commands::consul_catalog_node_services, + commands::consul_health_node, + commands::consul_health_checks, + commands::consul_health_service, + commands::consul_health_state, + commands::consul_agent_services, + commands::consul_agent_service, + commands::consul_agent_checks, + commands::consul_agent_register_service, + commands::consul_agent_deregister_service, + commands::consul_agent_service_maintenance, + commands::consul_agent_register_check, + commands::consul_agent_deregister_check, + commands::consul_agent_update_ttl, + commands::consul_sessions, + commands::consul_node_sessions, + commands::consul_session, + commands::consul_session_keys, + commands::consul_session_destroy_impact, + commands::consul_create_session, + commands::consul_renew_session, + commands::consul_destroy_session, + commands::consul_acquire_lock, + commands::consul_release_lock, + commands::consul_acl_list, + commands::consul_acl_token_self, + commands::consul_acl_token_clone, + commands::consul_acl_get, + commands::consul_acl_apply, + commands::consul_acl_references, + commands::consul_acl_delete, + commands::consul_enterprise_list, + commands::consul_enterprise_get, + commands::consul_enterprise_apply, + commands::consul_enterprise_impact, + commands::consul_enterprise_delete, + commands::consul_mesh_config_list, + commands::consul_mesh_config_get, + commands::consul_mesh_config_apply, + commands::consul_mesh_config_delete, + commands::consul_mesh_intentions_list, + commands::consul_mesh_intention_get, + commands::consul_mesh_intention_get_exact, + commands::consul_mesh_intention_upsert, + commands::consul_mesh_intention_delete, + commands::consul_mesh_intention_delete_exact, + commands::consul_mesh_intention_match, + commands::consul_mesh_intention_check, + commands::consul_mesh_discovery_chain, + commands::consul_mesh_peering_list, + commands::consul_mesh_peering_get, + commands::consul_mesh_peering_generate_token, + commands::consul_mesh_peering_establish, + commands::consul_mesh_peering_delete, + commands::consul_mesh_exported_services_list, + commands::consul_mesh_exported_services_apply, + ] +} + +pub fn route( + main_handler: impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static, +) -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static { + let consul_handler = invoke_handler(); + move |invoke| { + if handles(invoke.message.command()) { + consul_handler(invoke) + } else { + main_handler(invoke) + } + } +} + +#[cfg(test)] +mod tests { + use super::handles; + + #[test] + fn handles_only_consul_commands() { + assert!(handles("consul_capabilities")); + assert!(!handles("load_connections")); + } +} diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 2f72f448ac..9e5ad59d67 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -58,6 +58,7 @@ COPY crates/dbx-sqlite-worker/Cargo.toml crates/dbx-sqlite-worker/ COPY crates/dbx-web/Cargo.toml crates/dbx-web/ COPY crates/dbx-mcp/Cargo.toml crates/dbx-mcp/ COPY crates/dbx-cli/Cargo.toml crates/dbx-cli/ +COPY crates/dbx-tauri-consul/Cargo.toml crates/dbx-tauri-consul/ COPY src-tauri/Cargo.toml src-tauri/ # Copy every vendored crate: root Cargo.toml [patch.crates-io] points at # vendor/{ctor,rumqttc,dirs-sys,pageant,wry}; omitting any breaks the offline build. @@ -65,7 +66,7 @@ COPY vendor/ vendor/ RUN for crate in dbx-platform dbx-types dbx-sql dbx-sql-core dbx-sql-data dbx-sql-dialect dbx-sql-schema \ dbx-formats dbx-ai-provider dbx-plugin-runtime dbx-driver-agent dbx-driver-support \ dbx-driver-elasticsearch dbx-driver-mongodb dbx-driver-mysql dbx-driver-postgres \ - dbx-driver-redis dbx-driver-sqlserver dbx-drivers dbx-sqlite-worker; do \ + dbx-driver-redis dbx-driver-sqlserver dbx-drivers dbx-sqlite-worker dbx-tauri-consul; do \ mkdir -p "crates/$crate/src" && printf "" > "crates/$crate/src/lib.rs"; \ done \ && echo 'fn main() {}' > crates/dbx-sqlite-worker/src/main.rs diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5209320d3e..af22b5a3eb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -72,6 +72,7 @@ calamine = "0.30.1" zip = "4.6.1" dbx-core = { path = "../crates/dbx-core", default-features = false } dbx-mcp = { path = "../crates/dbx-mcp", default-features = false, features = ["duckdb-sidecar", "mq-admin"] } +dbx-tauri-consul = { path = "../crates/dbx-tauri-consul" } wry = { path = "../vendor/wry" } axum = { version = "0.8", features = ["ws"] } font-kit = "0.14.3" diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 25dd0f0c5a..122df7f923 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -8,7 +8,6 @@ pub mod config_cmd; pub mod connection; #[allow(dead_code, unused_imports)] mod connection_secrets; -pub mod consul_cmd; pub mod csv_export; pub mod data_compare; pub mod database_export; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d605389169..75086af559 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1804,7 +1804,7 @@ pub fn run() { request_app_close(app, "settings"); } }) - .invoke_handler(migration_gate::guard_handler(tauri::generate_handler![ + .invoke_handler(migration_gate::guard_handler(dbx_tauri_consul::route(tauri::generate_handler![ commands::ai::ai_complete, commands::ai::ai_stream, commands::ai::ai_agent_stream, @@ -2240,109 +2240,6 @@ pub fn run() { commands::zookeeper_cmd::zookeeper_get, commands::zookeeper_cmd::zookeeper_put, commands::zookeeper_cmd::zookeeper_delete, - commands::consul_cmd::consul_capabilities, - commands::consul_cmd::consul_txn, - commands::consul_cmd::consul_rename_key, - commands::consul_cmd::consul_blocking_query, - commands::consul_cmd::consul_domain_watch, - commands::consul_cmd::consul_cancel_blocking, - commands::consul_cmd::consul_watch_start, - commands::consul_cmd::consul_list_prefix, - commands::consul_cmd::consul_list_recursive, - commands::consul_cmd::consul_search, - commands::consul_cmd::consul_search_progress, - commands::consul_cmd::consul_cancel_search, - commands::consul_cmd::consul_export_bundle, - commands::consul_cmd::consul_import_preview, - commands::consul_cmd::consul_import_execute, - commands::consul_cmd::consul_delete_prefix_preview, - commands::consul_cmd::consul_delete_prefix_execute, - commands::consul_cmd::consul_get, - commands::consul_cmd::consul_put, - commands::consul_cmd::consul_delete, - commands::consul_cmd::consul_prepared_query_list, - commands::consul_cmd::consul_prepared_query_read, - commands::consul_cmd::consul_prepared_query_create, - commands::consul_cmd::consul_prepared_query_update, - commands::consul_cmd::consul_prepared_query_delete, - commands::consul_cmd::consul_prepared_query_execute, - commands::consul_cmd::consul_prepared_query_explain, - commands::consul_cmd::consul_event_list, - commands::consul_cmd::consul_event_fire, - commands::consul_cmd::consul_coordinate_nodes, - commands::consul_cmd::consul_operator_read, - commands::consul_cmd::consul_snapshot_generate, - commands::consul_cmd::consul_snapshot_restore, - commands::consul_cmd::consul_autopilot_update, - commands::consul_cmd::consul_raft_transfer, - commands::consul_cmd::consul_raft_remove, - commands::consul_cmd::consul_keyring_write, - commands::consul_cmd::consul_license_write, - commands::consul_cmd::consul_status_leader, - commands::consul_cmd::consul_status_peers, - commands::consul_cmd::consul_agent_self, - commands::consul_cmd::consul_agent_members, - commands::consul_cmd::consul_agent_metrics, - commands::consul_cmd::consul_catalog_datacenters, - commands::consul_cmd::consul_catalog_nodes, - commands::consul_cmd::consul_catalog_services, - commands::consul_cmd::consul_catalog_service_nodes, - commands::consul_cmd::consul_catalog_node_services, - commands::consul_cmd::consul_health_node, - commands::consul_cmd::consul_health_checks, - commands::consul_cmd::consul_health_service, - commands::consul_cmd::consul_health_state, - commands::consul_cmd::consul_agent_services, - commands::consul_cmd::consul_agent_service, - commands::consul_cmd::consul_agent_checks, - commands::consul_cmd::consul_agent_register_service, - commands::consul_cmd::consul_agent_deregister_service, - commands::consul_cmd::consul_agent_service_maintenance, - commands::consul_cmd::consul_agent_register_check, - commands::consul_cmd::consul_agent_deregister_check, - commands::consul_cmd::consul_agent_update_ttl, - commands::consul_cmd::consul_sessions, - commands::consul_cmd::consul_node_sessions, - commands::consul_cmd::consul_session, - commands::consul_cmd::consul_session_keys, - commands::consul_cmd::consul_session_destroy_impact, - commands::consul_cmd::consul_create_session, - commands::consul_cmd::consul_renew_session, - commands::consul_cmd::consul_destroy_session, - commands::consul_cmd::consul_acquire_lock, - commands::consul_cmd::consul_release_lock, - commands::consul_cmd::consul_acl_list, - commands::consul_cmd::consul_acl_token_self, - commands::consul_cmd::consul_acl_token_clone, - commands::consul_cmd::consul_acl_get, - commands::consul_cmd::consul_acl_apply, - commands::consul_cmd::consul_acl_references, - commands::consul_cmd::consul_acl_delete, - commands::consul_cmd::consul_enterprise_list, - commands::consul_cmd::consul_enterprise_get, - commands::consul_cmd::consul_enterprise_apply, - commands::consul_cmd::consul_enterprise_impact, - commands::consul_cmd::consul_enterprise_delete, - commands::consul_cmd::consul_mesh_config_list, - commands::consul_cmd::consul_mesh_config_get, - commands::consul_cmd::consul_mesh_config_apply, - commands::consul_cmd::consul_mesh_config_delete, - commands::consul_cmd::consul_mesh_intentions_list, - commands::consul_cmd::consul_mesh_intention_get, - commands::consul_cmd::consul_mesh_intention_get_exact, - commands::consul_cmd::consul_mesh_intention_upsert, - commands::consul_cmd::consul_mesh_intention_delete, - commands::consul_cmd::consul_mesh_intention_delete_exact, - commands::consul_cmd::consul_mesh_intention_match, - commands::consul_cmd::consul_mesh_intention_check, - commands::consul_cmd::consul_mesh_discovery_chain, - commands::consul_cmd::consul_mesh_peering_list, - commands::consul_cmd::consul_mesh_peering_get, - commands::consul_cmd::consul_mesh_peering_generate_token, - commands::consul_cmd::consul_mesh_peering_establish, - commands::consul_cmd::consul_mesh_peering_delete, - commands::consul_cmd::consul_mesh_exported_services_list, - commands::consul_cmd::consul_mesh_exported_services_apply, commands::nacos_cmd::nacos_test_connection, commands::nacos_cmd::nacos_list_namespaces, commands::nacos_cmd::nacos_sidebar_snapshot, @@ -2740,7 +2637,7 @@ pub fn run() { commands::tunnel_profiles::load_tunnel_profiles, commands::tunnel_profiles::save_tunnel_profiles, commands::tunnel_profiles::test_tunnel_profile, - ])) + ]))) .build(tauri::generate_context!()) .inspect(|app| { append_startup_probe(format!("tauri application built after {:?}", startup_begin.elapsed()));