aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/backend/Cargo.lock8
-rw-r--r--ui/backend/Cargo.toml6
-rw-r--r--ui/backend/src/dotfiles/mod.rs1
-rw-r--r--ui/backend/src/dotfiles/vars.rs57
-rw-r--r--ui/backend/src/main.rs3
-rw-r--r--ui/src/components/dotfiles/Vars.tsx194
-rw-r--r--ui/src/pages/Dotfiles.tsx90
-rw-r--r--ui/src/state/models.ts6
-rw-r--r--ui/src/state/store.ts9
9 files changed, 364 insertions, 10 deletions
diff --git a/ui/backend/Cargo.lock b/ui/backend/Cargo.lock
index abb72ce2..85736e2a 100644
--- a/ui/backend/Cargo.lock
+++ b/ui/backend/Cargo.lock
@@ -5285,18 +5285,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typed-builder"
-version = "0.18.1"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e"
+checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add"
dependencies = [
"typed-builder-macro",
]
[[package]]
name = "typed-builder-macro"
-version = "0.18.1"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352"
+checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63"
dependencies = [
"proc-macro2",
"quote",
diff --git a/ui/backend/Cargo.toml b/ui/backend/Cargo.toml
index 4e0019d3..9cf47436 100644
--- a/ui/backend/Cargo.toml
+++ b/ui/backend/Cargo.toml
@@ -12,10 +12,10 @@ edition = "2021"
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
-atuin-client = { path = "../../atuin-client", version = "18.2.0" }
-atuin-common = { path = "../../atuin-common", version = "18.2.0" }
+atuin-client = { path = "../../crates/atuin-client", version = "18.2.0" }
+atuin-common = { path = "../../crates/atuin-common", version = "18.2.0" }
-atuin-dotfiles = { path = "../../atuin-dotfiles", version = "0.2.0" }
+atuin-dotfiles = { path = "../../crates/atuin-dotfiles", version = "0.2.0" }
eyre = "0.6"
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
diff --git a/ui/backend/src/dotfiles/mod.rs b/ui/backend/src/dotfiles/mod.rs
index d293a01b..feafe783 100644
--- a/ui/backend/src/dotfiles/mod.rs
+++ b/ui/backend/src/dotfiles/mod.rs
@@ -1 +1,2 @@
pub mod aliases;
+pub mod vars;
diff --git a/ui/backend/src/dotfiles/vars.rs b/ui/backend/src/dotfiles/vars.rs
new file mode 100644
index 00000000..d8d5bd75
--- /dev/null
+++ b/ui/backend/src/dotfiles/vars.rs
@@ -0,0 +1,57 @@
+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/main.rs b/ui/backend/src/main.rs
index fbcf9481..fe6271b8 100644
--- a/ui/backend/src/main.rs
+++ b/ui/backend/src/main.rs
@@ -118,6 +118,9 @@ fn main() {
dotfiles::aliases::import_aliases,
dotfiles::aliases::delete_alias,
dotfiles::aliases::set_alias,
+ dotfiles::vars::vars,
+ dotfiles::vars::delete_var,
+ dotfiles::vars::set_var,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
diff --git a/ui/src/components/dotfiles/Vars.tsx b/ui/src/components/dotfiles/Vars.tsx
new file mode 100644
index 00000000..00317b23
--- /dev/null
+++ b/ui/src/components/dotfiles/Vars.tsx
@@ -0,0 +1,194 @@
+import { useEffect, useState } from "react";
+
+import DataTable from "@/components/ui/data-table";
+import { Button } from "@/components/ui/button";
+import { MoreHorizontal } from "lucide-react";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import { ColumnDef } from "@tanstack/react-table";
+
+import { invoke } from "@tauri-apps/api/core";
+import Drawer from "@/components/Drawer";
+
+import { Var } from "@/state/models";
+import { useStore } from "@/state/store";
+
+function deleteVar(name: string, refreshVars: () => void) {
+ invoke("delete_var", { name: name })
+ .then(() => {
+ refreshVars();
+ })
+ .catch(() => {
+ console.error("Failed to delete var");
+ });
+}
+
+function AddVar({ onAdd: onAdd }: { onAdd?: () => void }) {
+ let [name, setName] = useState("");
+ let [value, setValue] = useState("");
+ let [exp, setExport] = useState(false);
+
+ // simple form to add vars
+ return (
+ <div className="p-4">
+ <h2 className="text-xl font-semibold leading-6 text-gray-900">Add var</h2>
+ <p className="mt-2">Add a new var to your shell</p>
+
+ <form
+ className="mt-4"
+ onSubmit={(e) => {
+ e.preventDefault();
+
+ invoke("set_var", { name: name, value: value, export: exp })
+ .then(() => {
+ console.log("Added var");
+
+ if (onAdd) onAdd();
+ })
+ .catch(() => {
+ console.error("Failed to add var");
+ });
+ }}
+ >
+ <input
+ className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+ type="text"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ placeholder="Var name"
+ />
+
+ <input
+ className="mt-4 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
+ autoComplete="off"
+ autoCapitalize="off"
+ autoCorrect="off"
+ spellCheck="false"
+ type="text"
+ value={value}
+ onChange={(e) => setValue(e.target.value)}
+ placeholder="Var value"
+ />
+
+ <div>
+ <label>
+ <input
+ className="mt-4 bg-gray-50 mr-2 inline"
+ autoComplete="off"
+ autoCapitalize="off"
+ autoCorrect="off"
+ spellCheck="false"
+ type="checkbox"
+ value={exp}
+ onChange={(e) => setExport(e.target.checked)}
+ />
+ Export the var and make it visible to subprocesses
+ </label>
+ </div>
+
+ <input
+ type="submit"
+ className="block mt-4 rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
+ value="Add var"
+ />
+ </form>
+ </div>
+ );
+}
+
+export default function Vars() {
+ const vars = useStore((state) => state.vars);
+ const refreshVars = useStore((state) => state.refreshVars);
+
+ let [varDrawerOpen, setVarDrawerOpen] = useState(false);
+
+ const columns: ColumnDef<Var>[] = [
+ {
+ accessorKey: "name",
+ header: "Name",
+ },
+ {
+ accessorKey: "value",
+ header: "Value",
+ },
+ {
+ id: "actions",
+ cell: ({ row }: any) => {
+ const shell_var = row.original;
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" className="h-8 w-8 p-0 float-right">
+ <span className="sr-only">Open menu</span>
+ <MoreHorizontal className="h-4 w-4 text-right" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
+ <DropdownMenuItem
+ onClick={() => deleteVar(shell_var.name, refreshVars)}
+ >
+ Delete
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ );
+ },
+ },
+ ];
+
+ useEffect(() => {
+ refreshVars();
+ }, []);
+
+ return (
+ <div className="pt-10">
+ <div className="sm:flex sm:items-center">
+ <div className="sm:flex-auto">
+ <h1 className="text-base font-semibold leading-6 text-gray-900">
+ Vars
+ </h1>
+ <p className="mt-2 text-sm text-gray-700">
+ Configure environment variables here
+ </p>
+ </div>
+ <div className="mt-4 sm:ml-16 sm:mt-0 flex-row">
+ <Drawer
+ open={varDrawerOpen}
+ onOpenChange={setVarDrawerOpen}
+ width="30%"
+ trigger={
+ <button
+ type="button"
+ className="block rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
+ >
+ Add
+ </button>
+ }
+ >
+ <AddVar
+ onAdd={() => {
+ refreshVars();
+ setVarDrawerOpen(false);
+ }}
+ />
+ </Drawer>
+ </div>
+ </div>
+ <div className="mt-8 flow-root">
+ <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
+ <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
+ <DataTable columns={columns} data={vars} />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/ui/src/pages/Dotfiles.tsx b/ui/src/pages/Dotfiles.tsx
index 6b0870b3..29b6b54a 100644
--- a/ui/src/pages/Dotfiles.tsx
+++ b/ui/src/pages/Dotfiles.tsx
@@ -1,6 +1,35 @@
+import { useState } from "react";
import Aliases from "@/components/dotfiles/Aliases";
+import Vars from "@/components/dotfiles/Vars";
-function Header() {
+enum Section {
+ Aliases,
+ Vars,
+ Scripts,
+}
+
+function renderDotfiles(current: Section) {
+ switch (current) {
+ case Section.Aliases:
+ return <Aliases />;
+ case Section.Vars:
+ return <Vars />;
+ case Section.Scripts:
+ return <div />;
+ }
+}
+
+interface HeaderProps {
+ current: Section;
+ setCurrent: (section: Section) => void;
+}
+
+interface TabsProps {
+ current: Section;
+ setCurrent: (section: Section) => void;
+}
+
+function Header({ current, setCurrent }: HeaderProps) {
return (
<div className="md:flex md:items-center md:justify-between">
<div className="min-w-0 flex-1">
@@ -8,17 +37,72 @@ function Header() {
Dotfiles
</h2>
</div>
+
+ <Tabs current={current} setCurrent={setCurrent} />
+ </div>
+ );
+}
+
+function classNames(...classes) {
+ return classes.filter(Boolean).join(" ");
+}
+
+function Tabs({ current, setCurrent }: TabsProps) {
+ let tabs = [
+ {
+ name: "Aliases",
+ isCurrent: () => current === Section.Aliases,
+ section: Section.Aliases,
+ },
+ {
+ name: "Vars",
+ isCurrent: () => current === Section.Vars,
+ section: Section.Vars,
+ },
+ {
+ name: "Scripts",
+ isCurrent: () => current === Section.Scripts,
+ section: Section.Scripts,
+ },
+ ];
+
+ return (
+ <div>
+ <div className="mt-4">
+ <nav className="flex space-x-4" aria-label="Tabs">
+ {tabs.map((tab) => (
+ <button
+ onClick={() => {
+ setCurrent(tab.section);
+ }}
+ key={tab.name}
+ className={classNames(
+ tab.isCurrent()
+ ? "bg-gray-100 text-gray-700"
+ : "text-gray-500 hover:text-gray-700",
+ "rounded-md px-3 py-2 text-sm font-medium",
+ )}
+ aria-current={tab.isCurrent() ? "page" : undefined}
+ >
+ {tab.name}
+ </button>
+ ))}
+ </nav>
+ </div>
</div>
);
}
export default function Dotfiles() {
+ let [current, setCurrent] = useState(Section.Aliases);
+ console.log(current);
+
return (
<div className="pl-60">
<div className="p-10">
- <Header />
+ <Header current={current} setCurrent={setCurrent} />
Manage your shell aliases, variables and paths
- <Aliases />
+ {renderDotfiles(current)}
</div>
</div>
);
diff --git a/ui/src/state/models.ts b/ui/src/state/models.ts
index f11ce651..5afcb804 100644
--- a/ui/src/state/models.ts
+++ b/ui/src/state/models.ts
@@ -32,3 +32,9 @@ export interface Alias {
name: string;
value: string;
}
+
+export interface Var {
+ name: string;
+ value: string;
+ export: bool;
+}
diff --git a/ui/src/state/store.ts b/ui/src/state/store.ts
index 08410ba8..7e237d70 100644
--- a/ui/src/state/store.ts
+++ b/ui/src/state/store.ts
@@ -19,10 +19,12 @@ interface AtuinState {
user: User;
homeInfo: HomeInfo;
aliases: Alias[];
+ vars: Var[];
shellHistory: ShellHistory[];
refreshHomeInfo: () => void;
refreshAliases: () => void;
+ refreshVars: () => void;
refreshShellHistory: (query?: string) => void;
}
@@ -30,6 +32,7 @@ export const useStore = create<AtuinState>()((set) => ({
user: DefaultUser,
homeInfo: DefaultHomeInfo,
aliases: [],
+ vars: [],
shellHistory: [],
refreshAliases: () => {
@@ -38,6 +41,12 @@ export const useStore = create<AtuinState>()((set) => ({
});
},
+ refreshVars: () => {
+ invoke("vars").then((vars: any) => {
+ set({ vars: vars });
+ });
+ },
+
refreshShellHistory: (query?: string) => {
if (query) {
invoke("search", { query: query })