- Fix SQL injection in data.rs by wrapping get_table_data in READ ONLY transaction - Fix SQL injection in docker.rs CREATE DATABASE via escape_ident - Fix command injection in docker.rs by validating pg_version/container_name and escaping shell-interpolated values - Fix UTF-8 panic on stderr truncation with char_indices - Wrap delete_rows in a transaction for atomicity - Replace .expect() with proper error propagation in lib.rs - Cache AI settings in AppState to avoid repeated disk reads - Cap JSONB column discovery at 50 to prevent unbounded queries - Fix ERD colorMode to respect system theme via useTheme() - Extract AppState::get_pool() replacing ~19 inline pool patterns - Extract shared AiSettingsFields component (DRY popover + sheet) - Make get_connections_path pub(crate) and reuse from docker.rs - Deduplicate check_docker by delegating to check_docker_internal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
157 lines
5.9 KiB
Rust
157 lines
5.9 KiB
Rust
mod commands;
|
|
mod error;
|
|
mod mcp;
|
|
mod models;
|
|
mod state;
|
|
mod utils;
|
|
|
|
use models::settings::{AppSettings, DockerHost};
|
|
use state::AppState;
|
|
use std::sync::Arc;
|
|
use tauri::Manager;
|
|
|
|
pub fn run() {
|
|
let shared_state = Arc::new(AppState::new());
|
|
|
|
let _ = tauri::Builder::default()
|
|
.plugin(tauri_plugin_shell::init())
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.manage(shared_state)
|
|
.setup(|app| {
|
|
let state = app.state::<Arc<AppState>>().inner().clone();
|
|
let data_dir = app
|
|
.path()
|
|
.app_data_dir()
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
let connections_path = data_dir.join("connections.json");
|
|
|
|
// Read app settings
|
|
let settings_path = data_dir.join("app_settings.json");
|
|
|
|
let settings = if settings_path.exists() {
|
|
std::fs::read_to_string(&settings_path)
|
|
.ok()
|
|
.and_then(|data| serde_json::from_str::<AppSettings>(&data).ok())
|
|
.unwrap_or_default()
|
|
} else {
|
|
AppSettings::default()
|
|
};
|
|
|
|
// Apply docker host from settings
|
|
let docker_host = match settings.docker.host {
|
|
DockerHost::Remote => settings.docker.remote_url.clone(),
|
|
DockerHost::Local => None,
|
|
};
|
|
|
|
let mcp_enabled = settings.mcp.enabled;
|
|
let mcp_port = settings.mcp.port;
|
|
|
|
// Set docker host synchronously (state is fresh, no contention)
|
|
let state_for_setup = state.clone();
|
|
tauri::async_runtime::block_on(async {
|
|
*state_for_setup.docker_host.write().await = docker_host;
|
|
});
|
|
|
|
if mcp_enabled {
|
|
let shutdown_rx = state.mcp_shutdown_tx.subscribe();
|
|
let mcp_state = state.clone();
|
|
tauri::async_runtime::spawn(async move {
|
|
*mcp_state.mcp_running.write().await = true;
|
|
if let Err(e) =
|
|
mcp::start_mcp_server(mcp_state.clone(), connections_path, mcp_port, shutdown_rx)
|
|
.await
|
|
{
|
|
log::error!("MCP server error: {}", e);
|
|
}
|
|
*mcp_state.mcp_running.write().await = false;
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
// connections
|
|
commands::connections::get_connections,
|
|
commands::connections::save_connection,
|
|
commands::connections::delete_connection,
|
|
commands::connections::test_connection,
|
|
commands::connections::connect,
|
|
commands::connections::switch_database,
|
|
commands::connections::disconnect,
|
|
commands::connections::set_read_only,
|
|
commands::connections::get_read_only,
|
|
commands::connections::get_db_flavor,
|
|
// queries
|
|
commands::queries::execute_query,
|
|
// schema
|
|
commands::schema::list_databases,
|
|
commands::schema::list_schemas,
|
|
commands::schema::list_tables,
|
|
commands::schema::list_views,
|
|
commands::schema::list_functions,
|
|
commands::schema::list_indexes,
|
|
commands::schema::list_sequences,
|
|
commands::schema::get_table_columns,
|
|
commands::schema::get_table_constraints,
|
|
commands::schema::get_table_indexes,
|
|
commands::schema::get_completion_schema,
|
|
commands::schema::get_column_details,
|
|
commands::schema::get_table_triggers,
|
|
commands::schema::get_schema_erd,
|
|
// data
|
|
commands::data::get_table_data,
|
|
commands::data::update_row,
|
|
commands::data::insert_row,
|
|
commands::data::delete_rows,
|
|
// export
|
|
commands::export::export_csv,
|
|
commands::export::export_json,
|
|
// management
|
|
commands::management::get_database_info,
|
|
commands::management::create_database,
|
|
commands::management::drop_database,
|
|
commands::management::list_roles,
|
|
commands::management::create_role,
|
|
commands::management::alter_role,
|
|
commands::management::drop_role,
|
|
commands::management::get_table_privileges,
|
|
commands::management::grant_revoke,
|
|
commands::management::manage_role_membership,
|
|
commands::management::list_sessions,
|
|
commands::management::cancel_query,
|
|
commands::management::terminate_backend,
|
|
// history
|
|
commands::history::add_history_entry,
|
|
commands::history::get_history,
|
|
commands::history::clear_history,
|
|
// saved queries
|
|
commands::saved_queries::list_saved_queries,
|
|
commands::saved_queries::save_query,
|
|
commands::saved_queries::delete_saved_query,
|
|
// ai
|
|
commands::ai::get_ai_settings,
|
|
commands::ai::save_ai_settings,
|
|
commands::ai::list_ollama_models,
|
|
commands::ai::generate_sql,
|
|
commands::ai::explain_sql,
|
|
commands::ai::fix_sql_error,
|
|
// lookup
|
|
commands::lookup::entity_lookup,
|
|
// docker
|
|
commands::docker::check_docker,
|
|
commands::docker::list_tusk_containers,
|
|
commands::docker::clone_to_docker,
|
|
commands::docker::start_container,
|
|
commands::docker::stop_container,
|
|
commands::docker::remove_container,
|
|
// settings
|
|
commands::settings::get_app_settings,
|
|
commands::settings::save_app_settings,
|
|
commands::settings::get_mcp_status,
|
|
])
|
|
.run(tauri::generate_context!())
|
|
.inspect_err(|e| {
|
|
log::error!("Tauri application error: {}", e);
|
|
});
|
|
}
|