aboutsummaryrefslogtreecommitdiffstats
path: root/ui/backend/src
diff options
context:
space:
mode:
Diffstat (limited to 'ui/backend/src')
-rw-r--r--ui/backend/src/db.rs316
-rw-r--r--ui/backend/src/dotfiles/aliases.rs91
-rw-r--r--ui/backend/src/dotfiles/mod.rs2
-rw-r--r--ui/backend/src/dotfiles/vars.rs57
-rw-r--r--ui/backend/src/install.rs73
-rw-r--r--ui/backend/src/main.rs329
-rw-r--r--ui/backend/src/pty.rs124
-rw-r--r--ui/backend/src/run/migrations.rs13
-rw-r--r--ui/backend/src/run/mod.rs2
-rw-r--r--ui/backend/src/run/pty.rs103
-rw-r--r--ui/backend/src/state.rs10
-rw-r--r--ui/backend/src/store.rs1
12 files changed, 0 insertions, 1121 deletions
diff --git a/ui/backend/src/db.rs b/ui/backend/src/db.rs
deleted file mode 100644
index b08d8ebe..00000000
--- a/ui/backend/src/db.rs
+++ /dev/null
@@ -1,316 +0,0 @@
-// Some wrappers around the Atuin history DB
-// I'll probably use this to inform changes to the "upstream" client crate
-// We also use Strings a bunch for errors. They're passed to the Tauri frontend,
-// which requires that they be serializable.
-// Can rework that in the future too, but my main concern is avoiding tauri limitations/reqs
-// ending up in the main crate.
-
-use serde::Serialize;
-use sqlx::{sqlite::SqliteRow, Row};
-use std::collections::HashMap;
-use std::path::PathBuf;
-
-use atuin_client::settings::{FilterMode, SearchMode};
-use atuin_client::{
- database::{Context, Database, OptFilters, Sqlite},
- history::History,
-};
-use atuin_history::stats;
-
-// useful for preprocessing data for the frontend
-#[derive(Serialize, Debug)]
-pub struct NameValue<T> {
- pub name: String,
- pub value: T,
-}
-
-#[derive(Serialize, Debug)]
-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,
- pub last_30d: u64,
-}
-
-#[derive(Serialize, Debug)]
-pub struct UIHistory {
- pub id: String,
- /// When the command was run.
- pub timestamp: i128,
- /// How long the command took to run.
- pub duration: i64,
- /// The exit code of the command.
- pub exit: i64,
- /// The command that was run.
- pub command: String,
- /// The current working directory when the command was run.
- pub cwd: String,
- /// The session ID, associated with a terminal session.
- pub session: String,
- /// The hostname of the machine the command was run on.
- pub user: String,
-
- pub host: String,
-}
-
-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 mac = format!("/Users/{}", user);
- let linux = format!("/home/{}", user);
-
- 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,
- }
- }
-}
-
-pub struct HistoryDB(Sqlite);
-
-impl HistoryDB {
- pub async fn new(path: PathBuf, timeout: f64) -> Result<Self, String> {
- let sqlite = Sqlite::new(path, timeout)
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(Self(sqlite))
- }
-
- 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: 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())?;
-
- Ok(history)
- }
-
- pub async fn search(&self, offset: Option<u64>, query: &str) -> Result<Vec<UIHistory>, String> {
- let context = Context {
- session: "".to_string(),
- cwd: "".to_string(),
- host_id: "".to_string(),
- hostname: "".to_string(),
- git_root: None,
- };
-
- let filters = OptFilters {
- limit: Some(200),
- offset: offset.map(|offset| offset as i64),
- ..OptFilters::default()
- };
-
- let history = self
- .0
- .search(
- SearchMode::Fuzzy,
- FilterMode::Global,
- &context,
- query,
- filters,
- )
- .await
- .map_err(|e| e.to_string())?;
-
- let history = history
- .into_iter()
- .filter(|h| h.duration > 0)
- .map(|h| h.into())
- .collect();
-
- Ok(history)
- }
-
- pub async fn prefix_search(&self, query: &str) -> Result<Vec<UIHistory>, String> {
- let context = Context {
- session: "".to_string(),
- cwd: "".to_string(),
- host_id: "".to_string(),
- hostname: "".to_string(),
- git_root: None,
- };
-
- let filters = OptFilters {
- limit: Some(5),
- ..OptFilters::default()
- };
-
- let history = self
- .0
- .search(
- SearchMode::Prefix,
- FilterMode::Global,
- &context,
- query,
- filters,
- )
- .await
- .map_err(|e| e.to_string())?;
-
- let history = history
- .into_iter()
- .filter(|h| h.duration > 0)
- .map(|h| h.into())
- .collect();
-
- Ok(history)
- }
-
- pub async fn calendar(&self) -> Result<Vec<(String, u64)>, String> {
- let query = "select count(1) as count, strftime('%F', datetime(timestamp / 1000000000, 'unixepoch')) as day from history where timestamp > ((unixepoch() - 31536000) * 1000000000) group by day;";
-
- let calendar: Vec<(String, u64)> = sqlx::query(query)
- // safe to cast, count(x) is never < 0
- .map(|row: SqliteRow| {
- (
- row.get::<String, _>("day"),
- row.get::<i64, _>("count") as u64,
- )
- })
- .fetch_all(&self.0.pool)
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(calendar)
- }
-
- pub async fn global_stats(&self) -> Result<GlobalStats, String> {
- let day_ago = time::OffsetDateTime::now_utc() - time::Duration::days(1);
- let day_ago = day_ago.unix_timestamp_nanos();
-
- let week_ago = time::OffsetDateTime::now_utc() - time::Duration::days(7);
- let week_ago = week_ago.unix_timestamp_nanos();
-
- let month_ago = time::OffsetDateTime::now_utc() - time::Duration::days(30);
- let month_ago = month_ago.unix_timestamp_nanos();
-
- // get the last 30 days of shell history
- let history: Vec<UIHistory> = sqlx::query("SELECT * FROM history WHERE timestamp > ?")
- .bind(month_ago as i64)
- .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()
- })
- .map(|h: History| h.into())
- .fetch_all(&self.0.pool)
- .await
- .map_err(|e| e.to_string())?;
-
- let total: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM history")
- .fetch_one(&self.0.pool)
- .await
- .map_err(|e| e.to_string())?;
-
- let mut day = 0;
- let mut week = 0;
- let mut month = 0;
-
- let mut daily = HashMap::new();
- let ymd = time::format_description::parse("[year]-[month]-[day]").unwrap();
-
- for i in history {
- if i.timestamp > day_ago {
- day += 1;
- }
-
- if i.timestamp > week_ago {
- week += 1;
- }
-
- if i.timestamp > month_ago {
- month += 1;
-
- // get the start of the day, as a unix timestamp
- let date = time::OffsetDateTime::from_unix_timestamp_nanos(i.timestamp)
- .unwrap()
- .format(&ymd)
- .unwrap();
-
- daily.entry(date).and_modify(|v| *v += 1).or_insert(1);
- }
- }
-
- let mut daily: Vec<NameValue<u64>> = daily
- .into_iter()
- .map(|(k, v)| NameValue { name: k, value: v })
- .collect();
- daily.sort_by(|a, b| a.name.cmp(&b.name));
-
- Ok(GlobalStats {
- total_history: total.0 as u64,
- last_30d: month,
- last_7d: week,
- last_1d: day,
- daily,
- stats: None,
- })
- }
-}
diff --git a/ui/backend/src/dotfiles/aliases.rs b/ui/backend/src/dotfiles/aliases.rs
deleted file mode 100644
index 972466fe..00000000
--- a/ui/backend/src/dotfiles/aliases.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use std::path::PathBuf;
-
-use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
-use atuin_common::shell::Shell;
-use atuin_dotfiles::{
- shell::{existing_aliases, Alias},
- store::AliasStore,
-};
-
-async fn alias_store() -> eyre::Result<AliasStore> {
- let settings = Settings::new()?;
-
- let record_store_path = PathBuf::from(settings.record_store_path.as_str());
- let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
-
- let encryption_key: [u8; 32] = encryption::load_key(&settings)?.into();
-
- let host_id = Settings::host_id().expect("failed to get host_id");
-
- Ok(AliasStore::new(sqlite_store, host_id, encryption_key))
-}
-
-#[tauri::command]
-pub async fn aliases() -> Result<Vec<Alias>, String> {
- let alias_store = alias_store().await.map_err(|e| e.to_string())?;
-
- let aliases = alias_store
- .aliases()
- .await
- .map_err(|e| format!("failed to load aliases: {}", e))?;
-
- Ok(aliases)
-}
-
-#[tauri::command]
-pub async fn delete_alias(name: String) -> Result<(), String> {
- let alias_store = alias_store().await.map_err(|e| e.to_string())?;
-
- alias_store
- .delete(name.as_str())
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(())
-}
-
-#[tauri::command]
-pub async fn set_alias(name: String, value: String) -> Result<(), String> {
- let alias_store = alias_store().await.map_err(|e| e.to_string())?;
-
- alias_store
- .set(name.as_str(), value.as_str())
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(())
-}
-
-#[tauri::command]
-pub async fn import_aliases() -> Result<Vec<Alias>, String> {
- let store = alias_store().await.map_err(|e| e.to_string())?;
- let shell = Shell::default_shell().map_err(|e| e.to_string())?;
- let shell_name = shell.to_string();
-
- if !shell.is_posixish() {
- return Err(format!(
- "Default shell {shell_name} not supported for import"
- ));
- }
-
- let existing_aliases = existing_aliases(Some(shell)).map_err(|e| e.to_string())?;
- let store_aliases = store.aliases().await.map_err(|e| e.to_string())?;
-
- let mut res = Vec::new();
-
- for alias in existing_aliases {
- // O(n), but n is small, and imports infrequent
- // can always make a map
- if store_aliases.contains(&alias) {
- continue;
- }
-
- res.push(alias.clone());
- store
- .set(&alias.name, &alias.value)
- .await
- .map_err(|e| e.to_string())?;
- }
-
- Ok(res)
-}
diff --git a/ui/backend/src/dotfiles/mod.rs b/ui/backend/src/dotfiles/mod.rs
deleted file mode 100644
index feafe783..00000000
--- a/ui/backend/src/dotfiles/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod aliases;
-pub mod vars;
diff --git a/ui/backend/src/dotfiles/vars.rs b/ui/backend/src/dotfiles/vars.rs
deleted file mode 100644
index d8d5bd75..00000000
--- a/ui/backend/src/dotfiles/vars.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use std::path::PathBuf;
-
-use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
-use atuin_common::shell::Shell;
-use atuin_dotfiles::{
- shell::{existing_aliases, Alias, Var},
- store::var::VarStore,
-};
-
-async fn var_store() -> eyre::Result<VarStore> {
- let settings = Settings::new()?;
-
- let record_store_path = PathBuf::from(settings.record_store_path.as_str());
- let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
-
- let encryption_key: [u8; 32] = encryption::load_key(&settings)?.into();
-
- let host_id = Settings::host_id().expect("failed to get host_id");
-
- Ok(VarStore::new(sqlite_store, host_id, encryption_key))
-}
-
-#[tauri::command]
-pub async fn vars() -> Result<Vec<Var>, String> {
- let var_store = var_store().await.map_err(|e| e.to_string())?;
-
- let vars = var_store
- .vars()
- .await
- .map_err(|e| format!("failed to load aliases: {}", e))?;
-
- Ok(vars)
-}
-
-#[tauri::command]
-pub async fn delete_var(name: String) -> Result<(), String> {
- let var_store = var_store().await.map_err(|e| e.to_string())?;
-
- var_store
- .delete(name.as_str())
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(())
-}
-
-#[tauri::command]
-pub async fn set_var(name: String, value: String, export: bool) -> Result<(), String> {
- let var_store = var_store().await.map_err(|e| e.to_string())?;
-
- var_store
- .set(name.as_str(), value.as_str(), export)
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(())
-}
diff --git a/ui/backend/src/install.rs b/ui/backend/src/install.rs
deleted file mode 100644
index 17896e3a..00000000
--- a/ui/backend/src/install.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Handle installing the Atuin CLI
-// We can use the standard install script for this
-
-use std::process::Command;
-
-use tokio::{
- fs::{read_to_string, OpenOptions},
- io::AsyncWriteExt,
-};
-
-use atuin_common::shell::Shell;
-
-#[tauri::command]
-pub(crate) async fn install_cli() -> Result<(), String> {
- let output = Command::new("sh")
- .arg("-c")
- .arg("curl --proto '=https' --tlsv1.2 -LsSf https://github.com/atuinsh/atuin/releases/latest/download/atuin-installer.sh | sh")
- .output().map_err(|e|format!("Failed to execute Atuin installer: {e}"));
-
- Ok(())
-}
-
-#[tauri::command]
-pub(crate) async fn is_cli_installed() -> Result<bool, String> {
- let shell = Shell::default_shell().map_err(|e| format!("Failed to get default shell: {e}"))?;
- let output = if shell == Shell::Powershell {
- shell
- .run_interactive(&["atuin --version; if ($?) {echo 'ATUIN FOUND'}"])
- .map_err(|e| format!("Failed to run interactive command"))?
- } else {
- shell
- .run_interactive(&["atuin --version && echo 'ATUIN FOUND'"])
- .map_err(|e| format!("Failed to run interactive command"))?
- };
-
- Ok(output.contains("ATUIN FOUND"))
-}
-
-#[tauri::command]
-pub(crate) async fn setup_cli() -> Result<(), String> {
- let shell = Shell::default_shell().map_err(|e| format!("Failed to get default shell: {e}"))?;
- let config_file_path = shell.config_file();
-
- if config_file_path.is_none() {
- return Err("Failed to fetch default config file".to_string());
- }
-
- let config_file_path = config_file_path.unwrap();
- let config_file = read_to_string(config_file_path.clone())
- .await
- .map_err(|e| format!("Failed to read config file: {e}"))?;
-
- if config_file.contains("atuin init") {
- return Ok(());
- }
-
- let mut file = OpenOptions::new()
- .write(true)
- .append(true)
- .open(config_file_path)
- .await
- .unwrap();
-
- let config = format!(
- "if [ -x \"$(command -v atuin)\" ]; then eval \"$(atuin init {})\"; fi",
- shell.to_string()
- );
- file.write_all(config.as_bytes())
- .await
- .map_err(|e| format!("Failed to write Atuin shell init: {e}"));
-
- Ok(())
-}
diff --git a/ui/backend/src/main.rs b/ui/backend/src/main.rs
deleted file mode 100644
index eed6bfd3..00000000
--- a/ui/backend/src/main.rs
+++ /dev/null
@@ -1,329 +0,0 @@
-// Prevents additional console window on Windows in release, DO NOT REMOVE!!
-#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-
-use tauri::State;
-
-use std::path::PathBuf;
-
-use tauri::{AppHandle, Manager};
-use time::format_description::well_known::Rfc3339;
-
-mod db;
-mod dotfiles;
-mod install;
-mod pty;
-mod run;
-mod state;
-mod store;
-
-use atuin_client::settings::Settings;
-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;
-
-#[derive(Debug, serde::Serialize)]
-struct HomeInfo {
- pub record_count: u64,
- pub history_count: u64,
- pub username: Option<String>,
- pub last_sync: Option<String>,
- pub top_commands: Vec<(String, u64)>,
- pub recent_commands: Vec<UIHistory>,
-}
-
-#[tauri::command]
-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(offset.unwrap_or(0)), Some(100))
- .await?
- .into_iter()
- .map(|h| h.into())
- .collect();
-
- Ok(history)
-}
-
-#[tauri::command]
-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(offset, query.as_str()).await?;
-
- Ok(history)
-}
-
-#[tauri::command]
-async fn global_stats() -> Result<GlobalStats, 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 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 session() -> Result<String, String> {
- Settings::new()
- .map_err(|e| e.to_string())?
- .session_token()
- .map_err(|e| e.to_string())
-}
-
-#[tauri::command]
-async fn login(username: String, password: String, key: String) -> Result<String, String> {
- let settings = Settings::new().map_err(|e| e.to_string())?;
-
- let record_store_path = PathBuf::from(settings.record_store_path.as_str());
- let store = SqliteStore::new(record_store_path, settings.local_timeout)
- .await
- .map_err(|e| e.to_string())?;
-
- if settings.logged_in() {
- return Err(String::from("Already logged in"));
- }
-
- let session = atuin_client::login::login(&settings, &store, username, password, key)
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(session)
-}
-
-#[tauri::command]
-async fn logout() -> Result<(), String> {
- let settings = Settings::new().map_err(|e| e.to_string())?;
-
- atuin_client::logout::logout(&settings).map_err(|e| e.to_string())?;
-
- Ok(())
-}
-
-#[tauri::command]
-async fn register(username: String, email: String, password: String) -> Result<String, String> {
- let settings = Settings::new().map_err(|e| e.to_string())?;
-
- let session = atuin_client::register::register(&settings, username, email, password)
- .await
- .map_err(|e| e.to_string())?;
-
- Ok(session)
-}
-
-#[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());
- let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout)
- .await
- .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 last_sync = Settings::last_sync()
- .map_err(|e| e.to_string())?
- .format(&Rfc3339)
- .map_err(|e| e.to_string())?;
-
- let record_count = sqlite_store.len_all().await.map_err(|e| e.to_string())?;
- let history_count = sqlite_store
- .len_tag(HISTORY_TAG)
- .await
- .map_err(|e| e.to_string())?;
-
- let history = db.list(None, None).await?;
- let stats = stats::compute(&settings, &history, 10, 1)
- .map_or(vec![], |stats| stats.top[0..5].to_vec())
- .iter()
- .map(|(commands, count)| (commands.join(" "), *count as u64))
- .collect();
- let recent = if history.len() > 5 {
- history[0..5].to_vec()
- } else {
- vec![]
- };
- let recent = recent.into_iter().map(|h| h.into()).collect();
-
- let info = if !settings.logged_in() {
- HomeInfo {
- username: None,
- last_sync: None,
- record_count,
- history_count,
- top_commands: stats,
- recent_commands: recent,
- }
- } else {
- let client = atuin_client::api_client::Client::new(
- &settings.sync_address,
- settings
- .session_token()
- .map_err(|e| e.to_string())?
- .as_str(),
- settings.network_connect_timeout,
- settings.network_timeout,
- )
- .map_err(|e| e.to_string())?;
-
- let me = client.me().await.map_err(|e| e.to_string())?;
-
- HomeInfo {
- username: Some(me.username),
- last_sync: Some(last_sync.to_string()),
- record_count,
- history_count,
- top_commands: stats,
- recent_commands: recent,
- }
- };
-
- Ok(info)
-}
-
-// Match the format that the frontend library we use expects
-// All the processing in Rust, not JSunwrap.
-// Faaaassssssst af ⚡️🦀
-#[derive(Debug, serde::Serialize)]
-pub struct HistoryCalendarDay {
- pub date: String,
- pub count: u64,
- pub level: u8,
-}
-
-#[tauri::command]
-async fn history_calendar() -> Result<Vec<HistoryCalendarDay>, 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 calendar = db.calendar().await?;
-
- // probs don't want to iterate _this_ many times, but it's only the last year. so 365
- // iterations at max. should be quick.
-
- let max = calendar
- .iter()
- .max_by_key(|d| d.1)
- .expect("Can't find max count");
-
- let ret = calendar
- .iter()
- .map(|d| {
- // calculate the "level". we have 5, so figure out which 5th it fits into
- let percent: f64 = d.1 as f64 / max.1 as f64;
- let level = if d.1 == 0 {
- 0.0
- } else {
- (percent / 0.2).round() + 1.0
- };
-
- HistoryCalendarDay {
- date: d.0.clone(),
- count: d.1,
- level: std::cmp::min(4, level as u8),
- }
- })
- .collect();
-
- Ok(ret)
-}
-
-#[tauri::command]
-async fn prefix_search(query: &str) -> Result<Vec<String>, 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.prefix_search(query).await?;
- let commands = history.into_iter().map(|h| h.command).collect();
-
- Ok(commands)
-}
-
-#[tauri::command]
-async fn cli_settings() -> Result<Settings, String> {
- let settings = Settings::new().map_err(|e| e.to_string())?;
- Ok(settings)
-}
-
-fn show_window(app: &AppHandle) {
- let windows = app.webview_windows();
-
- windows
- .values()
- .next()
- .expect("Sorry, no window found")
- .set_focus()
- .expect("Can't Bring Window to Focus");
-}
-
-fn main() {
- tauri::Builder::default()
- .plugin(tauri_plugin_dialog::init())
- .plugin(tauri_plugin_os::init())
- .plugin(tauri_plugin_shell::init())
- .invoke_handler(tauri::generate_handler![
- list,
- search,
- prefix_search,
- global_stats,
- aliases,
- home_info,
- config,
- session,
- login,
- logout,
- register,
- history_calendar,
- cli_settings,
- run::pty::pty_open,
- run::pty::pty_write,
- run::pty::pty_resize,
- run::pty::pty_kill,
- install::install_cli,
- install::is_cli_installed,
- install::setup_cli,
- dotfiles::aliases::import_aliases,
- dotfiles::aliases::delete_alias,
- dotfiles::aliases::set_alias,
- dotfiles::vars::vars,
- dotfiles::vars::delete_var,
- dotfiles::vars::set_var,
- ])
- .plugin(
- tauri_plugin_sql::Builder::default()
- .add_migrations("sqlite:runbooks.db", run::migrations::migrations())
- .build(),
- )
- .plugin(tauri_plugin_http::init())
- .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
- let _ = show_window(app);
- }))
- .manage(state::AtuinState::default())
- .setup(|app| Ok(()))
- .run(tauri::generate_context!())
- .expect("error while running tauri application");
-}
diff --git a/ui/backend/src/pty.rs b/ui/backend/src/pty.rs
deleted file mode 100644
index af394d95..00000000
--- a/ui/backend/src/pty.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-use std::{
- io::Write,
- sync::{Arc, Mutex},
-};
-
-use bytes::Bytes;
-use eyre::{eyre, Result};
-use portable_pty::{CommandBuilder, MasterPty, PtySize};
-
-pub struct Pty {
- tx: tokio::sync::mpsc::Sender<Bytes>,
-
- pub master: Arc<Mutex<Box<dyn MasterPty + Send>>>,
- pub reader: Arc<Mutex<Box<dyn std::io::Read + Send>>>,
- pub child: Arc<Mutex<Box<dyn portable_pty::Child + Send>>>,
-}
-
-impl Pty {
- pub async fn open<'a>(rows: u16, cols: u16, cwd: Option<String>) -> Result<Self> {
- let sys = portable_pty::native_pty_system();
-
- let pair = sys
- .openpty(PtySize {
- rows,
- cols,
- pixel_width: 0,
- pixel_height: 0,
- })
- .map_err(|e| eyre!("Failed to open pty: {}", e))?;
-
- let mut cmd = CommandBuilder::new_default_prog();
-
- if let Some(cwd) = cwd {
- cmd.cwd(cwd);
- }
-
- let child = pair.slave.spawn_command(cmd).unwrap();
- drop(pair.slave);
-
- // Handle input -> write to master writer
- let (master_tx, mut master_rx) = tokio::sync::mpsc::channel::<Bytes>(32);
-
- let mut writer = pair.master.take_writer().unwrap();
- let reader = pair
- .master
- .try_clone_reader()
- .map_err(|e| e.to_string())
- .expect("Failed to clone reader");
-
- tokio::spawn(async move {
- while let Some(bytes) = master_rx.recv().await {
- writer.write_all(&bytes).unwrap();
- writer.flush().unwrap();
- }
-
- // When the channel has been closed, we won't be getting any more input. Close the
- // writer and the master.
- // This will also close the writer, which sends EOF to the underlying shell. Ensuring
- // that is also closed.
- drop(writer);
- });
-
- Ok(Pty {
- tx: master_tx,
- master: Arc::new(Mutex::new(pair.master)),
- reader: Arc::new(Mutex::new(reader)),
- child: Arc::new(Mutex::new(child)),
- })
- }
-
- pub async fn resize(&self, rows: u16, cols: u16) -> Result<()> {
- let master = self
- .master
- .lock()
- .map_err(|e| eyre!("Failed to lock pty master: {e}"))?;
-
- master
- .resize(PtySize {
- rows,
- cols,
- pixel_width: 0,
- pixel_height: 0,
- })
- .map_err(|e| eyre!("Failed to resize terminal: {e}"))?;
-
- Ok(())
- }
-
- pub async fn send_bytes(&self, bytes: Bytes) -> Result<()> {
- self.tx
- .send(bytes)
- .await
- .map_err(|e| eyre!("Failed to write to master tx: {}", e))
- }
-
- pub async fn send_string(&self, cmd: &str) -> Result<()> {
- let bytes: Vec<u8> = cmd.bytes().collect();
- let bytes = Bytes::from(bytes);
-
- self.send_bytes(bytes).await
- }
-
- pub async fn send_single_string(&self, cmd: &str) -> Result<()> {
- let mut bytes: Vec<u8> = cmd.bytes().collect();
- bytes.push(0x04);
-
- let bytes = Bytes::from(bytes);
-
- self.send_bytes(bytes).await
- }
-
- pub async fn kill_child(&self) -> Result<()> {
- let mut child = self
- .child
- .lock()
- .map_err(|e| eyre!("Failed to lock pty child: {e}"))?;
-
- child
- .kill()
- .map_err(|e| eyre!("Failed to kill child: {e}"))?;
-
- Ok(())
- }
-}
diff --git a/ui/backend/src/run/migrations.rs b/ui/backend/src/run/migrations.rs
deleted file mode 100644
index 3516e62a..00000000
--- a/ui/backend/src/run/migrations.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use lazy_static::lazy_static;
-use tauri_plugin_sql::{Builder, Migration, MigrationKind};
-
-pub fn migrations() -> Vec<Migration> {
- vec![
- Migration {
- version: 1,
- description: "create_initial_tables",
- sql: "CREATE TABLE runbooks(id string PRIMARY KEY, name TEXT, content TEXT, created bigint, updated bigint);",
- kind: MigrationKind::Up,
- }
- ]
-}
diff --git a/ui/backend/src/run/mod.rs b/ui/backend/src/run/mod.rs
deleted file mode 100644
index a7a28497..00000000
--- a/ui/backend/src/run/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod migrations;
-pub mod pty;
diff --git a/ui/backend/src/run/pty.rs b/ui/backend/src/run/pty.rs
deleted file mode 100644
index 72ca98d2..00000000
--- a/ui/backend/src/run/pty.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use eyre::{Result, WrapErr};
-use std::io::BufRead;
-use std::path::PathBuf;
-
-use crate::state::AtuinState;
-use tauri::{Emitter, Manager, State};
-
-use atuin_client::{database::Sqlite, record::sqlite_store::SqliteStore, settings::Settings};
-
-#[tauri::command]
-pub async fn pty_open<'a>(
- app: tauri::AppHandle,
- state: State<'a, AtuinState>,
- cwd: Option<String>,
-) -> Result<uuid::Uuid, String> {
- let id = uuid::Uuid::new_v4();
-
- let cwd = cwd.map(|c| shellexpand::tilde(c.as_str()).to_string());
- let pty = crate::pty::Pty::open(24, 80, cwd).await.unwrap();
-
- let reader = pty.reader.clone();
-
- tauri::async_runtime::spawn_blocking(move || loop {
- let mut buf = [0u8; 512];
-
- match reader.lock().unwrap().read(&mut buf) {
- // EOF
- Ok(0) => {
- println!("reader loop hit eof");
- break;
- }
-
- Ok(n) => {
- println!("read {n} bytes");
-
- // TODO: sort inevitable encoding issues
- let out = String::from_utf8_lossy(&buf).to_string();
- let out = out.trim_matches(char::from(0));
- let channel = format!("pty-{id}");
-
- app.emit(channel.as_str(), out).unwrap();
- }
-
- Err(e) => {
- println!("failed to read: {e}");
- break;
- }
- }
- });
-
- state.pty_sessions.write().await.insert(id, pty);
-
- Ok(id)
-}
-
-#[tauri::command]
-pub(crate) async fn pty_write(
- pid: uuid::Uuid,
- data: String,
- state: tauri::State<'_, AtuinState>,
-) -> Result<(), String> {
- let sessions = state.pty_sessions.read().await;
- let pty = sessions.get(&pid).ok_or("Pty not found")?;
-
- let bytes = data.as_bytes().to_vec();
- pty.send_bytes(bytes.into())
- .await
- .map_err(|e| e.to_string())?;
- Ok(())
-}
-
-#[tauri::command]
-pub(crate) async fn pty_resize(
- pid: uuid::Uuid,
- rows: u16,
- cols: u16,
- state: tauri::State<'_, AtuinState>,
-) -> Result<(), String> {
- let sessions = state.pty_sessions.read().await;
- let pty = sessions.get(&pid).ok_or("Pty not found")?;
-
- pty.resize(rows, cols).await.map_err(|e| e.to_string())?;
-
- Ok(())
-}
-
-#[tauri::command]
-pub(crate) async fn pty_kill(
- pid: uuid::Uuid,
- state: tauri::State<'_, AtuinState>,
-) -> Result<(), String> {
- let pty = state.pty_sessions.write().await.remove(&pid);
-
- match pty {
- Some(pty) => {
- pty.kill_child().await.map_err(|e| e.to_string())?;
- println!("RIP {pid:?}");
- }
- None => {}
- }
-
- Ok(())
-}
diff --git a/ui/backend/src/state.rs b/ui/backend/src/state.rs
deleted file mode 100644
index de53b4c5..00000000
--- a/ui/backend/src/state.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use std::collections::HashMap;
-use std::sync::Mutex;
-use tauri::async_runtime::RwLock;
-
-use crate::pty::Pty;
-
-#[derive(Default)]
-pub(crate) struct AtuinState {
- pub pty_sessions: RwLock<HashMap<uuid::Uuid, Pty>>,
-}
diff --git a/ui/backend/src/store.rs b/ui/backend/src/store.rs
deleted file mode 100644
index 8b137891..00000000
--- a/ui/backend/src/store.rs
+++ /dev/null
@@ -1 +0,0 @@
-