aboutsummaryrefslogtreecommitdiffstats
path: root/ui/backend
diff options
context:
space:
mode:
Diffstat (limited to 'ui/backend')
-rw-r--r--ui/backend/Cargo.lock132
-rw-r--r--ui/backend/Cargo.toml6
-rw-r--r--ui/backend/capabilities/migrated.json19
-rw-r--r--ui/backend/src/db.rs106
-rw-r--r--ui/backend/src/main.rs28
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");
}