mirror of
https://github.com/atuinsh/atuin.git
synced 2025-01-24 07:09:39 +01: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]]
|
||||
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",
|
||||
|
@ -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"] }
|
||||
|
@ -1 +1,2 @@
|
||||
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::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");
|
||||
|
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 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>
|
||||
);
|
||||
|
@ -32,3 +32,9 @@ export interface Alias {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Var {
|
||||
name: string;
|
||||
value: string;
|
||||
export: bool;
|
||||
}
|
||||
|
@ -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 })
|
||||
|
Loading…
Reference in New Issue
Block a user