Editor settings, SSH/SSL runtime, data import + table-menu loose ends (#7)
* feat: editor settings model + typed clamped defaults (Task 1) * feat: carry SSH config + ssh_password to backend DbConfig (Task 2) * feat: Change enum bulk/drop/empty + type-specific change payload builder (Task 3) * feat: csv parser + shared export util (Task 4) * feat: rustls TLS connector factory with modes + client auth (Task 5) * feat: real SSH tunnel manager (testable backend) + pool eviction hook (Task 6) * feat: table DDL fetch (sqlite + pg_dump arg builder) (Task 7) * feat: keychain SSH secrets + connection delete purge + tunnel lifecycle (Task 8) * feat: real SSH tunnel + TLS connect path for postgres/mysql (Task 9) * feat: execute_change bulk/drop/empty + get_table_ddl command (Task 10) * feat: fetch SSH secrets into dbConnect + save on connection form (Task 11) * feat: Editor settings tab UI (Task 12) * feat: QueryEditor applies editor settings live (Task 13) * feat: ImportDialog with CSV/JSON preview + column mapping (Task 14) * feat: table-menu export/empty/delete/import + queue labels + payload builder (Task 15) * fix: error sanitization, encrypted-key guard, row-indexed import errors, caps (Task 16) * docs: mark Editor Settings, SSH/SSL runtime, Data Import, table-menu loose ends shipped (Task 17) * feat: auto-refresh schema tree after schema-modifying SQL (query + queue drop) * fix: use theme-consistent red classes for danger menu items (text-error was undefined) * feat: changes queue as tab-bar popover + amber pending border * feat: redesign changes popover (visual/SQL toggle, cards, footer actions, Cmd+S) * refactor: drop per-card status label from changes popover cards * feat: green completion indicator on committed cards + auto-close tabs of dropped tables * docs: changes queue popover UX + auto schema refresh statuses
This commit is contained in:
@@ -43,6 +43,7 @@ pub struct ConnectionInput {
|
||||
pub ssh_user: Option<String>,
|
||||
pub ssh_auth_method: Option<String>,
|
||||
pub ssh_private_key_path: Option<String>,
|
||||
pub ssh_password: Option<String>,
|
||||
pub ssh_passphrase: Option<String>,
|
||||
pub ssl_mode: Option<String>,
|
||||
pub ssl_ca_path: Option<String>,
|
||||
@@ -71,6 +72,7 @@ mod tests {
|
||||
ssh_user: Some("tunnel".to_string()),
|
||||
ssh_auth_method: Some("Key".to_string()),
|
||||
ssh_private_key_path: Some("/path/to/key".to_string()),
|
||||
ssh_password: Some("ssh-pw".to_string()),
|
||||
ssh_passphrase: Some("passphrase".to_string()),
|
||||
ssl_mode: Some("require".to_string()),
|
||||
ssl_ca_path: Some("/path/to/ca".to_string()),
|
||||
@@ -96,6 +98,7 @@ mod tests {
|
||||
assert_eq!(deserialized.ssh_user, Some("tunnel".to_string()));
|
||||
assert_eq!(deserialized.ssh_auth_method, Some("Key".to_string()));
|
||||
assert_eq!(deserialized.ssh_private_key_path, Some("/path/to/key".to_string()));
|
||||
assert_eq!(deserialized.ssh_password, Some("ssh-pw".to_string()));
|
||||
assert_eq!(deserialized.ssh_passphrase, Some("passphrase".to_string()));
|
||||
assert_eq!(deserialized.ssl_mode, Some("require".to_string()));
|
||||
assert_eq!(deserialized.ssl_ca_path, Some("/path/to/ca".to_string()));
|
||||
|
||||
@@ -135,6 +135,23 @@ pub enum Change {
|
||||
sql: String,
|
||||
rollback_sql: String,
|
||||
},
|
||||
BulkInsert {
|
||||
id: String,
|
||||
schema: String,
|
||||
table: String,
|
||||
columns: Vec<String>,
|
||||
rows: Vec<Vec<serde_json::Value>>,
|
||||
},
|
||||
DropTable {
|
||||
id: String,
|
||||
schema: String,
|
||||
table: String,
|
||||
},
|
||||
EmptyTable {
|
||||
id: String,
|
||||
schema: String,
|
||||
table: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Change {
|
||||
@@ -143,7 +160,10 @@ impl Change {
|
||||
Change::Update { id, .. }
|
||||
| Change::Insert { id, .. }
|
||||
| Change::Delete { id, .. }
|
||||
| Change::AlterTable { id, .. } => id,
|
||||
| Change::AlterTable { id, .. }
|
||||
| Change::BulkInsert { id, .. }
|
||||
| Change::DropTable { id, .. }
|
||||
| Change::EmptyTable { id, .. } => id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,6 +299,35 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_bulk_insert_roundtrip() {
|
||||
let json = serde_json::json!({
|
||||
"type": "bulk_insert", "id": "x", "schema": "public", "table": "t",
|
||||
"columns": ["a", "b"],
|
||||
"rows": [[1, "y"], [2, "z"]]
|
||||
});
|
||||
let c: Change = serde_json::from_value(json).unwrap();
|
||||
match c {
|
||||
Change::BulkInsert { columns, rows, .. } => {
|
||||
assert_eq!(columns, vec!["a".to_string(), "b".to_string()]);
|
||||
assert_eq!(rows.len(), 2);
|
||||
}
|
||||
_ => panic!("expected BulkInsert"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_drop_and_empty_roundtrip() {
|
||||
let drop: Change = serde_json::from_value(serde_json::json!({
|
||||
"type": "drop_table", "id": "d", "schema": "public", "table": "t"
|
||||
})).unwrap();
|
||||
assert_eq!(drop.id(), "d");
|
||||
let empty: Change = serde_json::from_value(serde_json::json!({
|
||||
"type": "empty_table", "id": "e", "schema": "public", "table": "t"
|
||||
})).unwrap();
|
||||
assert_eq!(empty.id(), "e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_info_fk_ref() {
|
||||
let col = ColumnInfo {
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod backup;
|
||||
pub mod connection;
|
||||
pub mod db_viewer;
|
||||
pub mod folder;
|
||||
pub mod ssh;
|
||||
pub mod tag;
|
||||
pub mod settings;
|
||||
|
||||
@@ -10,4 +11,5 @@ pub use connection::{Connection, ConnectionInput};
|
||||
pub use db_viewer::{Change, ColumnInfo, FilterRule, Pagination, QueryResult, SortRule, TableInfo};
|
||||
pub use folder::{Folder, FolderInput};
|
||||
pub use settings::Settings;
|
||||
pub use ssh::SshConfig;
|
||||
pub use tag::{Tag, TagInput};
|
||||
@@ -13,4 +13,10 @@ pub struct Settings {
|
||||
pub table_page_size: i64,
|
||||
pub shortcuts: HashMap<String, String>,
|
||||
pub accent_color: String,
|
||||
// Editor (Plan A)
|
||||
pub editor_font_size: i64,
|
||||
pub editor_font_family: String,
|
||||
pub editor_word_wrap: String,
|
||||
pub editor_minimap: bool,
|
||||
pub editor_tab_size: i64,
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// SSH tunnel configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SshConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub user: String,
|
||||
/// "password" or "key"
|
||||
pub auth_method: String,
|
||||
pub password: Option<String>,
|
||||
pub private_key_path: Option<String>,
|
||||
pub passphrase: Option<String>,
|
||||
}
|
||||
|
||||
impl SshConfig {
|
||||
/// Create a new `SshConfig` with the required fields.
|
||||
pub fn new(
|
||||
host: String,
|
||||
port: u16,
|
||||
user: String,
|
||||
auth_method: String,
|
||||
) -> Self {
|
||||
SshConfig {
|
||||
host,
|
||||
port,
|
||||
user,
|
||||
auth_method,
|
||||
password: None,
|
||||
private_key_path: None,
|
||||
passphrase: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate SSH configuration.
|
||||
///
|
||||
/// Returns `true` if:
|
||||
/// - `host` is not empty
|
||||
/// - `port` is in range 1..=65535 (u16 guarantees <= 65535)
|
||||
/// - `user` is not empty
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.host.is_empty() && self.port >= 1 && !self.user.is_empty()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user