Add four developer/QA features: - Connection color coding: color picker in dialog, colored indicators across toolbar, tabs, status bar, and connection selectors - Query history: Rust backend with JSON file storage (500 entry cap), sidebar panel with search/clear, auto-recording from workspace - Schema-aware SQL autocomplete: backend fetches column metadata, CodeMirror receives schema namespace with public tables unprefixed - EXPLAIN ANALYZE visualizer: recursive tree view with cost-colored bars, expand/collapse nodes, buffers info, Results/Explain tab toggle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
2.0 KiB
Rust
77 lines
2.0 KiB
Rust
use crate::error::{TuskError, TuskResult};
|
|
use crate::models::history::HistoryEntry;
|
|
use std::fs;
|
|
use tauri::{AppHandle, Manager};
|
|
|
|
fn get_history_path(app: &AppHandle) -> TuskResult<std::path::PathBuf> {
|
|
let dir = app
|
|
.path()
|
|
.app_data_dir()
|
|
.map_err(|e| TuskError::Custom(e.to_string()))?;
|
|
fs::create_dir_all(&dir)?;
|
|
Ok(dir.join("query_history.json"))
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn add_history_entry(app: AppHandle, entry: HistoryEntry) -> TuskResult<()> {
|
|
let path = get_history_path(&app)?;
|
|
let mut entries = if path.exists() {
|
|
let data = fs::read_to_string(&path)?;
|
|
serde_json::from_str::<Vec<HistoryEntry>>(&data).unwrap_or_default()
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
entries.insert(0, entry);
|
|
entries.truncate(500);
|
|
|
|
let data = serde_json::to_string_pretty(&entries)?;
|
|
fs::write(&path, data)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn get_history(
|
|
app: AppHandle,
|
|
connection_id: Option<String>,
|
|
search: Option<String>,
|
|
limit: Option<usize>,
|
|
) -> TuskResult<Vec<HistoryEntry>> {
|
|
let path = get_history_path(&app)?;
|
|
if !path.exists() {
|
|
return Ok(vec![]);
|
|
}
|
|
let data = fs::read_to_string(&path)?;
|
|
let entries: Vec<HistoryEntry> = serde_json::from_str(&data).unwrap_or_default();
|
|
|
|
let filtered: Vec<HistoryEntry> = entries
|
|
.into_iter()
|
|
.filter(|e| {
|
|
if let Some(ref cid) = connection_id {
|
|
if &e.connection_id != cid {
|
|
return false;
|
|
}
|
|
}
|
|
if let Some(ref s) = search {
|
|
let lower = s.to_lowercase();
|
|
if !e.sql.to_lowercase().contains(&lower) {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
})
|
|
.take(limit.unwrap_or(100))
|
|
.collect();
|
|
|
|
Ok(filtered)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn clear_history(app: AppHandle) -> TuskResult<()> {
|
|
let path = get_history_path(&app)?;
|
|
if path.exists() {
|
|
fs::remove_file(&path)?;
|
|
}
|
|
Ok(())
|
|
}
|