mirror of
https://github.com/atuinsh/atuin.git
synced 2025-06-23 19:31:36 +02:00
feat(ui/dotfiles): add vars (#1989)
This commit is contained in:
parent
150bcb8eb8
commit
cea48a1545
8
ui/backend/Cargo.lock
generated
8
ui/backend/Cargo.lock
generated
@ -5285,18 +5285,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-builder"
|
name = "typed-builder"
|
||||||
version = "0.18.1"
|
version = "0.18.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e"
|
checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typed-builder-macro",
|
"typed-builder-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-builder-macro"
|
name = "typed-builder-macro"
|
||||||
version = "0.18.1"
|
version = "0.18.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352"
|
checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -12,10 +12,10 @@ edition = "2021"
|
|||||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atuin-client = { path = "../../atuin-client", version = "18.2.0" }
|
atuin-client = { path = "../../crates/atuin-client", version = "18.2.0" }
|
||||||
atuin-common = { path = "../../atuin-common", 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"
|
eyre = "0.6"
|
||||||
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
|
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub mod aliases;
|
pub mod aliases;
|
||||||
|
pub mod vars;
|
||||||
|
57
ui/backend/src/dotfiles/vars.rs
Normal file
57
ui/backend/src/dotfiles/vars.rs
Normal file
@ -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(())
|
||||||
|
}
|
@ -118,6 +118,9 @@ fn main() {
|
|||||||
dotfiles::aliases::import_aliases,
|
dotfiles::aliases::import_aliases,
|
||||||
dotfiles::aliases::delete_alias,
|
dotfiles::aliases::delete_alias,
|
||||||
dotfiles::aliases::set_alias,
|
dotfiles::aliases::set_alias,
|
||||||
|
dotfiles::vars::vars,
|
||||||
|
dotfiles::vars::delete_var,
|
||||||
|
dotfiles::vars::set_var,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
194
ui/src/components/dotfiles/Vars.tsx
Normal file
194
ui/src/components/dotfiles/Vars.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,35 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import Aliases from "@/components/dotfiles/Aliases";
|
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 (
|
return (
|
||||||
<div className="md:flex md:items-center md:justify-between">
|
<div className="md:flex md:items-center md:justify-between">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@ -8,17 +37,72 @@ function Header() {
|
|||||||
Dotfiles
|
Dotfiles
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Dotfiles() {
|
export default function Dotfiles() {
|
||||||
|
let [current, setCurrent] = useState(Section.Aliases);
|
||||||
|
console.log(current);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pl-60">
|
<div className="pl-60">
|
||||||
<div className="p-10">
|
<div className="p-10">
|
||||||
<Header />
|
<Header current={current} setCurrent={setCurrent} />
|
||||||
Manage your shell aliases, variables and paths
|
Manage your shell aliases, variables and paths
|
||||||
<Aliases />
|
{renderDotfiles(current)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -32,3 +32,9 @@ export interface Alias {
|
|||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Var {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
export: bool;
|
||||||
|
}
|
||||||
|
@ -19,10 +19,12 @@ interface AtuinState {
|
|||||||
user: User;
|
user: User;
|
||||||
homeInfo: HomeInfo;
|
homeInfo: HomeInfo;
|
||||||
aliases: Alias[];
|
aliases: Alias[];
|
||||||
|
vars: Var[];
|
||||||
shellHistory: ShellHistory[];
|
shellHistory: ShellHistory[];
|
||||||
|
|
||||||
refreshHomeInfo: () => void;
|
refreshHomeInfo: () => void;
|
||||||
refreshAliases: () => void;
|
refreshAliases: () => void;
|
||||||
|
refreshVars: () => void;
|
||||||
refreshShellHistory: (query?: string) => void;
|
refreshShellHistory: (query?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ export const useStore = create<AtuinState>()((set) => ({
|
|||||||
user: DefaultUser,
|
user: DefaultUser,
|
||||||
homeInfo: DefaultHomeInfo,
|
homeInfo: DefaultHomeInfo,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
vars: [],
|
||||||
shellHistory: [],
|
shellHistory: [],
|
||||||
|
|
||||||
refreshAliases: () => {
|
refreshAliases: () => {
|
||||||
@ -38,6 +41,12 @@ export const useStore = create<AtuinState>()((set) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshVars: () => {
|
||||||
|
invoke("vars").then((vars: any) => {
|
||||||
|
set({ vars: vars });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
refreshShellHistory: (query?: string) => {
|
refreshShellHistory: (query?: string) => {
|
||||||
if (query) {
|
if (query) {
|
||||||
invoke("search", { query: query })
|
invoke("search", { query: query })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user