Skip to content

Commit 95cef71

Browse files
authored
feat(gui): background terminals and more (#2303)
* fixes & allow for background terminals to stay running * status indicators etc
1 parent fde86d3 commit 95cef71

8 files changed

Lines changed: 430 additions & 204 deletions

File tree

ui/backend/src/run/pty.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,17 @@ pub(crate) async fn pty_kill(
8686
pid: uuid::Uuid,
8787
state: tauri::State<'_, AtuinState>,
8888
) -> Result<(), String> {
89-
let pty = state.pty_sessions.write().await.remove(&pid).unwrap();
90-
pty.kill_child().await.map_err(|e|e.to_string())?;
91-
println!("RIP {pid:?}");
89+
let pty = state.pty_sessions.write().await.remove(&pid);
90+
91+
match pty {
92+
Some(pty)=>{
93+
94+
pty.kill_child().await.map_err(|e|e.to_string())?;
95+
println!("RIP {pid:?}");
96+
}
97+
None=>{}
98+
}
99+
92100

93101
Ok(())
94102
}

ui/src/App.tsx

Lines changed: 110 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -131,127 +131,136 @@ function App() {
131131

132132
return (
133133
<div
134-
className="flex h-dvh w-screen select-none"
135-
style={{ maxWidth: "100vw" }}
134+
className="flex w-screen select-none"
135+
style={{ maxWidth: "100vw", height: "calc(100dvh - 2rem)" }}
136136
>
137-
<div className="relative flex h-full flex-col !border-r-small border-divider transition-width pb-6 pt-9 items-center">
138-
<div className="flex items-center gap-0 px-3 justify-center">
139-
<div className="flex h-8 w-8">
140-
<img src={icon} alt="icon" className="h-8 w-8" />
137+
<div className="flex w-full">
138+
<div className="relative flex flex-col !border-r-small border-divider transition-width pb-6 pt-4 items-center">
139+
<div className="flex items-center gap-0 px-3 justify-center">
140+
<div className="flex h-8 w-8">
141+
<img src={icon} alt="icon" className="h-8 w-8" />
142+
</div>
141143
</div>
142-
</div>
143-
144-
<ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6">
145-
<Sidebar
146-
defaultSelectedKey="home"
147-
isCompact={true}
148-
items={navigation}
149-
/>
150-
</ScrollShadow>
151144

152-
<Spacer y={2} />
145+
<ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6">
146+
<Sidebar
147+
defaultSelectedKey="home"
148+
isCompact={true}
149+
items={navigation}
150+
className="z-50"
151+
/>
152+
</ScrollShadow>
153153

154-
<div className="flex items-center gap-3 px-3">
155-
<Dropdown showArrow placement="right-start">
156-
<DropdownTrigger>
157-
<Button disableRipple isIconOnly radius="full" variant="light">
158-
<Avatar
159-
isBordered
160-
className="flex-none"
161-
size="sm"
162-
name={user.username || ""}
163-
/>
164-
</Button>
165-
</DropdownTrigger>
166-
<DropdownMenu aria-label="Custom item styles">
167-
<DropdownItem
168-
key="profile"
169-
isReadOnly
170-
className="h-14 opacity-100"
171-
textValue="Signed in as"
172-
>
173-
<User
174-
avatarProps={{
175-
size: "sm",
176-
name: user.username || "Anonymous User",
177-
showFallback: true,
178-
imgProps: {
179-
className: "transition-none",
180-
},
181-
}}
182-
classNames={{
183-
name: "text-default-600",
184-
description: "text-default-500",
185-
}}
186-
description={
187-
user.bio || (user.username && "No bio") || "Sign up now"
188-
}
189-
name={user.username || "Anonymous User"}
190-
/>
191-
</DropdownItem>
154+
<Spacer y={2} />
192155

193-
<DropdownItem
194-
key="settings"
195-
description="Configure Atuin"
196-
startContent={<Icon icon="solar:settings-linear" width={24} />}
197-
>
198-
Settings
199-
</DropdownItem>
156+
<div className="flex items-center gap-3 px-3">
157+
<Dropdown showArrow placement="right-start">
158+
<DropdownTrigger>
159+
<Button disableRipple isIconOnly radius="full" variant="light">
160+
<Avatar
161+
isBordered
162+
className="flex-none"
163+
size="sm"
164+
name={user.username || ""}
165+
/>
166+
</Button>
167+
</DropdownTrigger>
168+
<DropdownMenu aria-label="Custom item styles">
169+
<DropdownItem
170+
key="profile"
171+
isReadOnly
172+
className="h-14 opacity-100"
173+
textValue="Signed in as"
174+
>
175+
<User
176+
avatarProps={{
177+
size: "sm",
178+
name: user.username || "Anonymous User",
179+
showFallback: true,
180+
imgProps: {
181+
className: "transition-none",
182+
},
183+
}}
184+
classNames={{
185+
name: "text-default-600",
186+
description: "text-default-500",
187+
}}
188+
description={
189+
user.bio || (user.username && "No bio") || "Sign up now"
190+
}
191+
name={user.username || "Anonymous User"}
192+
/>
193+
</DropdownItem>
200194

201-
<DropdownSection aria-label="Help & Feedback">
202195
<DropdownItem
203-
key="help_and_feedback"
204-
description="Get in touch"
205-
onPress={() => open("https://forum.atuin.sh")}
196+
key="settings"
197+
description="Configure Atuin"
206198
startContent={
207-
<Icon width={24} icon="solar:question-circle-linear" />
199+
<Icon icon="solar:settings-linear" width={24} />
208200
}
209201
>
210-
Help & Feedback
202+
Settings
211203
</DropdownItem>
212204

213-
{(user.username && (
205+
<DropdownSection aria-label="Help & Feedback">
214206
<DropdownItem
215-
key="logout"
207+
key="help_and_feedback"
208+
description="Get in touch"
209+
onPress={() => open("https://forum.atuin.sh")}
216210
startContent={
217-
<Icon width={24} icon="solar:logout-broken" />
211+
<Icon width={24} icon="solar:question-circle-linear" />
218212
}
219-
onClick={() => {
220-
logout();
221-
refreshUser();
222-
}}
223-
>
224-
Log Out
225-
</DropdownItem>
226-
)) || (
227-
<DropdownItem
228-
key="signup"
229-
description="Sync, backup and share your data"
230-
className="bg-emerald-100"
231-
startContent={<KeyRoundIcon size="18px" />}
232-
onPress={onOpen}
233213
>
234-
Log in or Register
214+
Help & Feedback
235215
</DropdownItem>
236-
)}
237-
</DropdownSection>
238-
</DropdownMenu>
239-
</Dropdown>
216+
217+
{(user.username && (
218+
<DropdownItem
219+
key="logout"
220+
startContent={
221+
<Icon width={24} icon="solar:logout-broken" />
222+
}
223+
onClick={() => {
224+
logout();
225+
refreshUser();
226+
}}
227+
>
228+
Log Out
229+
</DropdownItem>
230+
)) || (
231+
<DropdownItem
232+
key="signup"
233+
description="Sync, backup and share your data"
234+
className="bg-emerald-100"
235+
startContent={<KeyRoundIcon size="18px" />}
236+
onPress={onOpen}
237+
>
238+
Log in or Register
239+
</DropdownItem>
240+
)}
241+
</DropdownSection>
242+
</DropdownMenu>
243+
</Dropdown>
244+
</div>
240245
</div>
241-
</div>
242246

243-
{renderMain(section)}
247+
{renderMain(section)}
244248

245-
<Toaster />
246-
<Modal isOpen={isOpen} onOpenChange={onOpenChange} placement="top-center">
247-
<ModalContent className="p-8">
248-
{(onClose) => (
249-
<>
250-
<LoginOrRegister onClose={onClose} />
251-
</>
252-
)}
253-
</ModalContent>
254-
</Modal>
249+
<Toaster />
250+
<Modal
251+
isOpen={isOpen}
252+
onOpenChange={onOpenChange}
253+
placement="top-center"
254+
>
255+
<ModalContent className="p-8">
256+
{(onClose) => (
257+
<>
258+
<LoginOrRegister onClose={onClose} />
259+
</>
260+
)}
261+
</ModalContent>
262+
</Modal>
263+
</div>
255264
</div>
256265
);
257266
}

ui/src/components/runbooks/List.tsx

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
DropdownTrigger,
1515
DropdownMenu,
1616
DropdownItem,
17+
Badge,
1718
} from "@nextui-org/react";
1819

1920
import { EllipsisVerticalIcon } from "lucide-react";
@@ -22,32 +23,39 @@ import { DateTime } from "luxon";
2223

2324
import { NotebookPenIcon } from "lucide-react";
2425
import Runbook from "@/state/runbooks/runbook";
25-
import { useStore } from "@/state/store";
26+
import { AtuinState, useStore } from "@/state/store";
2627

2728
const NoteSidebar = () => {
28-
const runbooks = useStore((state) => state.runbooks);
29-
const refreshRunbooks = useStore((state) => state.refreshRunbooks);
29+
const runbooks = useStore((state: AtuinState) => state.runbooks);
30+
const refreshRunbooks = useStore(
31+
(state: AtuinState) => state.refreshRunbooks,
32+
);
3033

31-
const currentRunbook = useStore((state) => state.currentRunbook);
32-
const setCurrentRunbook = useStore((state) => state.setCurrentRunbook);
34+
const currentRunbook = useStore((state: AtuinState) => state.currentRunbook);
35+
const setCurrentRunbook = useStore(
36+
(state: AtuinState) => state.setCurrentRunbook,
37+
);
38+
const runbookInfo = useStore((state: AtuinState) => state.runbookInfo);
3339

3440
useEffect(() => {
3541
refreshRunbooks();
3642
}, []);
3743

3844
return (
39-
<div className="min-w-48 h-screen flex flex-col border-r-1">
45+
<div className="w-48 flex flex-col border-r-1">
4046
<div className="overflow-y-auto flex-grow">
4147
<Listbox
4248
hideSelectedIcon
43-
items={runbooks}
49+
items={runbooks.map((runbook) => {
50+
return [runbook, runbookInfo[runbook.id]];
51+
})}
4452
variant="flat"
4553
aria-label="Runbook list"
4654
selectionMode="single"
4755
selectedKeys={[currentRunbook]}
4856
itemClasses={{ base: "data-[selected=true]:bg-gray-200" }}
4957
topContent={
50-
<ButtonGroup>
58+
<ButtonGroup className="z-20">
5159
<Tooltip showArrow content="New Runbook" closeDelay={50}>
5260
<Button
5361
isIconOnly
@@ -66,7 +74,7 @@ const NoteSidebar = () => {
6674
</ButtonGroup>
6775
}
6876
>
69-
{(runbook) => (
77+
{([runbook, info]) => (
7078
<ListboxItem
7179
key={runbook.id}
7280
onPress={() => {
@@ -75,14 +83,26 @@ const NoteSidebar = () => {
7583
textValue={runbook.name || "Untitled"}
7684
endContent={
7785
<Dropdown>
78-
<DropdownTrigger className="bg-transparent">
79-
<Button isIconOnly>
80-
<EllipsisVerticalIcon
81-
size="16px"
82-
className="bg-transparent"
83-
/>
84-
</Button>
85-
</DropdownTrigger>
86+
<Badge
87+
content={info?.ptys}
88+
color="primary"
89+
style={
90+
info && info?.ptys > 0
91+
? {}
92+
: {
93+
display: "none",
94+
}
95+
}
96+
>
97+
<DropdownTrigger className="bg-transparent">
98+
<Button isIconOnly>
99+
<EllipsisVerticalIcon
100+
size="16px"
101+
className="bg-transparent"
102+
/>
103+
</Button>
104+
</DropdownTrigger>
105+
</Badge>
86106
<DropdownMenu aria-label="Dynamic Actions">
87107
<DropdownItem
88108
key={"delete"}

ui/src/components/runbooks/editor/Editor.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import "@blocknote/core/fonts/inter.css";
44
import "@blocknote/mantine/style.css";
55
import "./index.css";
66

7+
import { Spinner } from "@nextui-org/react";
8+
79
import {
810
BlockNoteSchema,
911
BlockNoteEditor,
@@ -102,7 +104,7 @@ export default function Editor() {
102104

103105
const debouncedOnChange = useDebounceCallback(onChange, 1000);
104106

105-
const fetchName = (): String => {
107+
const fetchName = (): string => {
106108
// Infer the title from the first text block
107109

108110
let blocks = editor.document;
@@ -119,7 +121,11 @@ export default function Editor() {
119121
};
120122

121123
if (editor === undefined) {
122-
return "Loading content...";
124+
return (
125+
<div className="flex w-full h-full flex-col justify-center items-center">
126+
<Spinner />
127+
</div>
128+
);
123129
}
124130

125131
// Renders the editor instance.

0 commit comments

Comments
 (0)