diff options
| author | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-05-06 08:11:47 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-06 08:11:47 +0100 |
| commit | 754ddeaa8d3e3e4f3efc93d5bb22c68c31bb5c36 (patch) | |
| tree | f48fb912c2be2d08855e97ff24b6919a115c3c4f /ui/backend | |
| parent | chore(deps): bump serde_with from 3.7.0 to 3.8.1 (#2002) (diff) | |
| download | atuin-754ddeaa8d3e3e4f3efc93d5bb22c68c31bb5c36.zip | |
feat(ui): scroll history infinitely (#1999)
* wip, history scrolls right!
* wip
* virtual scroll fucking worksssss
* paging works :)
* scroll search results now too
Diffstat (limited to '')
| -rw-r--r-- | ui/backend/Cargo.lock | 132 | ||||
| -rw-r--r-- | ui/backend/Cargo.toml | 6 | ||||
| -rw-r--r-- | ui/backend/capabilities/migrated.json | 19 | ||||
| -rw-r--r-- | ui/backend/src/db.rs | 106 | ||||
| -rw-r--r-- | ui/backend/src/main.rs | 28 |
5 files changed, 226 insertions, 65 deletions
diff --git a/ui/backend/Cargo.lock b/ui/backend/Cargo.lock index 85736e2a..c8eace7c 100644 --- a/ui/backend/Cargo.lock +++ b/ui/backend/Cargo.lock @@ -289,6 +289,36 @@ dependencies = [ ] [[package]] +name = "atuin-history" +version = "0.1.0" +dependencies = [ + "async-trait", + "atuin-client", + "atuin-common", + "base64 0.21.7", + "crossterm", + "directories", + "eyre", + "fs-err", + "futures-util", + "indicatif", + "interim", + "itertools", + "log", + "semver", + "serde", + "serde_json", + "sysinfo", + "time", + "tokio", + "tracing", + "unicode-segmentation", + "unicode-width", + "uuid", + "whoami", +] + +[[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -861,6 +891,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "filedescriptor", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1325,6 +1381,17 @@ dependencies = [ ] [[package]] +name = "filedescriptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" +dependencies = [ + "libc", + "thiserror", + "winapi", +] + +[[package]] name = "finl_unicode" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2602,6 +2669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -4115,6 +4183,27 @@ dependencies = [ ] [[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4820,6 +4909,41 @@ dependencies = [ ] [[package]] +name = "tauri-plugin" +version = "2.0.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6baaee0a083db1e04a1b7a3b0670d86a4d95dd2a54e7cbfb5547762b8ed098d9" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml 0.8.12", + "walkdir", +] + +[[package]] +name = "tauri-plugin-sql" +version = "2.0.0-beta.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c17360ef831e2789aab5439c241c8d7787ebd7c4fc40540181539b151f93bb" +dependencies = [ + "futures-core", + "log", + "serde", + "serde_json", + "sqlx", + "tauri", + "tauri-plugin", + "thiserror", + "time", + "tokio", +] + +[[package]] name = "tauri-runtime" version = "2.0.0-beta.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4864,16 +4988,16 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.0-beta.11" +version = "2.0.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a148adf8077e1891c8b7d1c2be90c1c8eb8c7a071c35bb8edbdfe7cd9d8e23c" +checksum = "d4709765385f035338ecc330f3fba753b8ee283c659c235da9768949cdb25469" dependencies = [ "brotli", "cargo_metadata", "ctor", "dunce", "glob", - "heck 0.4.1", + "heck 0.5.0", "html5ever", "infer", "json-patch", @@ -5316,6 +5440,7 @@ dependencies = [ "atuin-client", "atuin-common", "atuin-dotfiles", + "atuin-history", "eyre", "serde", "serde_json", @@ -5323,6 +5448,7 @@ dependencies = [ "syntect", "tauri", "tauri-build", + "tauri-plugin-sql", "time", "uuid", ] diff --git a/ui/backend/Cargo.toml b/ui/backend/Cargo.toml index 9cf47436..1bc40b02 100644 --- a/ui/backend/Cargo.toml +++ b/ui/backend/Cargo.toml @@ -14,8 +14,8 @@ tauri-build = { version = "2.0.0-beta", features = [] } [dependencies] atuin-client = { path = "../../crates/atuin-client", version = "18.2.0" } atuin-common = { path = "../../crates/atuin-common", version = "18.2.0" } - atuin-dotfiles = { path = "../../crates/atuin-dotfiles", version = "0.2.0" } +atuin-history = { path = "../../crates/atuin-history", version = "0.1.0" } eyre = "0.6" tauri = { version = "2.0.0-beta", features = ["tray-icon"] } @@ -36,3 +36,7 @@ custom-protocol = ["tauri/custom-protocol"] #[lib] #crate-type = ["staticlib", "cdylib", "rlib"] + +[dependencies.tauri-plugin-sql] +features = ["sqlite"] # or "postgres", or "mysql" +version = "2.0.0-beta" diff --git a/ui/backend/capabilities/migrated.json b/ui/backend/capabilities/migrated.json index a47f2d7b..70b8b077 100644 --- a/ui/backend/capabilities/migrated.json +++ b/ui/backend/capabilities/migrated.json @@ -2,9 +2,7 @@ "identifier": "migrated", "description": "permissions that were migrated from v1", "context": "local", - "windows": [ - "main" - ], + "windows": ["main"], "permissions": [ "path:default", "event:default", @@ -12,13 +10,10 @@ "app:default", "resources:default", "menu:default", - "tray:default" + "tray:default", + "sql:allow-load", + "sql:allow-execute", + "sql:allow-select" ], - "platforms": [ - "linux", - "macOS", - "windows", - "android", - "iOS" - ] -}
\ No newline at end of file + "platforms": ["linux", "macOS", "windows", "android", "iOS"] +} diff --git a/ui/backend/src/db.rs b/ui/backend/src/db.rs index c1aa4de9..7e29302a 100644 --- a/ui/backend/src/db.rs +++ b/ui/backend/src/db.rs @@ -15,6 +15,7 @@ use atuin_client::{ database::{Context, Database, OptFilters, Sqlite}, history::History, }; +use atuin_history::stats; // useful for preprocessing data for the frontend #[derive(Serialize, Debug)] @@ -28,6 +29,7 @@ pub struct GlobalStats { pub total_history: u64, pub daily: Vec<NameValue<u64>>, + pub stats: Option<stats::Stats>, pub last_1d: u64, pub last_7d: u64, @@ -55,31 +57,33 @@ pub struct UIHistory { pub host: String, } -pub fn to_ui_history(history: History) -> UIHistory { - let parts: Vec<String> = history.hostname.split(':').map(str::to_string).collect(); +impl From<History> for UIHistory { + fn from(history: History) -> Self { + let parts: Vec<String> = history.hostname.split(':').map(str::to_string).collect(); - let (host, user) = if parts.len() == 2 { - (parts[0].clone(), parts[1].clone()) - } else { - ("no-host".to_string(), "no-user".to_string()) - }; + let (host, user) = if parts.len() == 2 { + (parts[0].clone(), parts[1].clone()) + } else { + ("no-host".to_string(), "no-user".to_string()) + }; - let mac = format!("/Users/{}", user); - let linux = format!("/home/{}", user); + let mac = format!("/Users/{}", user); + let linux = format!("/home/{}", user); - let cwd = history.cwd.replace(mac.as_str(), "~"); - let cwd = cwd.replace(linux.as_str(), "~"); + let cwd = history.cwd.replace(mac.as_str(), "~"); + let cwd = cwd.replace(linux.as_str(), "~"); - UIHistory { - id: history.id.0, - timestamp: history.timestamp.unix_timestamp_nanos(), - duration: history.duration, - exit: history.exit, - command: history.command, - session: history.session, - host, - user, - cwd, + UIHistory { + id: history.id.0, + timestamp: history.timestamp.unix_timestamp_nanos(), + duration: history.duration, + exit: history.exit, + command: history.command, + session: history.session, + host, + user, + cwd, + } } } @@ -94,35 +98,47 @@ impl HistoryDB { Ok(Self(sqlite)) } - pub async fn list(&self, limit: Option<usize>, unique: bool) -> Result<Vec<UIHistory>, String> { - let filters = vec![]; - - // bit of a hack but provide an empty context - // shell context makes _no sense_ in a GUI - let context = Context { - session: "".to_string(), - cwd: "".to_string(), - host_id: "".to_string(), - hostname: "".to_string(), - git_root: None, + pub async fn list( + &self, + offset: Option<u64>, + limit: Option<usize>, + ) -> Result<Vec<History>, String> { + let query = if let Some(limit) = limit { + sqlx::query("select * from history order by timestamp desc limit ?1 offset ?2") + .bind(limit as i64) + .bind(offset.unwrap_or(0) as i64) + } else { + sqlx::query("select * from history order by timestamp desc") }; - let history = self - .0 - .list(&filters, &context, limit, unique, false) + let history: Vec<History> = query + .map(|row: SqliteRow| { + History::from_db() + .id(row.get("id")) + .timestamp( + time::OffsetDateTime::from_unix_timestamp_nanos( + row.get::<i64, _>("timestamp") as i128, + ) + .unwrap(), + ) + .duration(row.get("duration")) + .exit(row.get("exit")) + .command(row.get("command")) + .cwd(row.get("cwd")) + .session(row.get("session")) + .hostname(row.get("hostname")) + .deleted_at(None) + .build() + .into() + }) + .fetch_all(&self.0.pool) .await .map_err(|e| e.to_string())?; - let history = history - .into_iter() - .filter(|h| h.duration > 0) - .map(to_ui_history) - .collect(); - Ok(history) } - pub async fn search(&self, query: &str) -> Result<Vec<UIHistory>, String> { + pub async fn search(&self, offset: Option<u64>, query: &str) -> Result<Vec<UIHistory>, String> { let context = Context { session: "".to_string(), cwd: "".to_string(), @@ -133,6 +149,7 @@ impl HistoryDB { let filters = OptFilters { limit: Some(200), + offset: offset.map(|offset| offset as i64), ..OptFilters::default() }; @@ -151,7 +168,7 @@ impl HistoryDB { let history = history .into_iter() .filter(|h| h.duration > 0) - .map(to_ui_history) + .map(|h| h.into()) .collect(); Ok(history) @@ -189,7 +206,7 @@ impl HistoryDB { .build() .into() }) - .map(to_ui_history) + .map(|h: History| h.into()) .fetch_all(&self.0.pool) .await .map_err(|e| e.to_string())?; @@ -240,6 +257,7 @@ impl HistoryDB { last_7d: week, last_1d: day, daily, + stats: None, }) } } diff --git a/ui/backend/src/main.rs b/ui/backend/src/main.rs index fe6271b8..ce248d61 100644 --- a/ui/backend/src/main.rs +++ b/ui/backend/src/main.rs @@ -13,6 +13,7 @@ mod store; use atuin_client::{ encryption, history::HISTORY_TAG, record::sqlite_store::SqliteStore, record::store::Store, }; +use atuin_history::stats; use db::{GlobalStats, HistoryDB, UIHistory}; use dotfiles::aliases::aliases; @@ -25,25 +26,30 @@ struct HomeInfo { } #[tauri::command] -async fn list() -> Result<Vec<UIHistory>, String> { +async fn list(offset: Option<u64>) -> Result<Vec<UIHistory>, String> { let settings = Settings::new().map_err(|e| e.to_string())?; let db_path = PathBuf::from(settings.db_path.as_str()); let db = HistoryDB::new(db_path, settings.local_timeout).await?; - let history = db.list(Some(100), false).await?; + let history = db + .list(Some(offset.unwrap_or(0)), Some(100)) + .await? + .into_iter() + .map(|h| h.into()) + .collect(); Ok(history) } #[tauri::command] -async fn search(query: String) -> Result<Vec<UIHistory>, String> { +async fn search(query: String, offset: Option<u64>) -> Result<Vec<UIHistory>, String> { let settings = Settings::new().map_err(|e| e.to_string())?; let db_path = PathBuf::from(settings.db_path.as_str()); let db = HistoryDB::new(db_path, settings.local_timeout).await?; - let history = db.search(query.as_str()).await?; + let history = db.search(offset, query.as_str()).await?; Ok(history) } @@ -54,12 +60,22 @@ async fn global_stats() -> Result<GlobalStats, String> { let db_path = PathBuf::from(settings.db_path.as_str()); let db = HistoryDB::new(db_path, settings.local_timeout).await?; - let stats = db.global_stats().await?; + let mut stats = db.global_stats().await?; + + let history = db.list(None, None).await?; + let history_stats = stats::compute(&settings, &history, 10, 1); + + stats.stats = history_stats; Ok(stats) } #[tauri::command] +async fn config() -> Result<Settings, String> { + Settings::new().map_err(|e| e.to_string()) +} + +#[tauri::command] async fn home_info() -> Result<HomeInfo, String> { let settings = Settings::new().map_err(|e| e.to_string())?; let record_store_path = PathBuf::from(settings.record_store_path.as_str()); @@ -115,6 +131,7 @@ fn main() { global_stats, aliases, home_info, + config, dotfiles::aliases::import_aliases, dotfiles::aliases::delete_alias, dotfiles::aliases::set_alias, @@ -122,6 +139,7 @@ fn main() { dotfiles::vars::delete_var, dotfiles::vars::set_var, ]) + .plugin(tauri_plugin_sql::Builder::default().build()) .run(tauri::generate_context!()) .expect("error while running tauri application"); } |
