Skip to content

Commit 1fd7935

Browse files
committed
feat: add spinner while ssh operations are happening
1 parent 073b7fe commit 1fd7935

File tree

5 files changed

+47
-15
lines changed

5 files changed

+47
-15
lines changed

crates/atuin-desktop-runtime/src/blocks/script.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,9 @@ impl Script {
748748
let error_msg = format!("Failed to start SSH execution: {}", e);
749749
let _ = context.block_failed(error_msg.to_string()).await;
750750
if let Some(ref path) = remote_temp_path {
751-
let _ = ssh_pool.delete_file(&hostname, username.as_deref(), path).await;
751+
let _ = ssh_pool
752+
.delete_file(&hostname, username.as_deref(), path)
753+
.await;
752754
}
753755
return (Err(error_msg.into()), Vec::new(), None);
754756
}

crates/atuin-desktop-runtime/src/blocks/terminal.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,9 @@ impl Terminal {
249249
}
250250
} else {
251251
if uses_output_vars {
252-
fs_var_handle =
253-
Some(crate::context::fs_var::setup().map_err(|e| {
254-
format!("Failed to setup temp file for output variables: {}", e)
255-
})?);
252+
fs_var_handle = Some(crate::context::fs_var::setup().map_err(|e| {
253+
format!("Failed to setup temp file for output variables: {}", e)
254+
})?);
256255
} else {
257256
fs_var_handle = None;
258257
}

src/components/runbooks/editor/blocks/Script/Script.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ const ScriptBlock = ({
128128
const blockContext = useBlockContext(script.id);
129129
const sshParent = blockContext.sshHost;
130130

131+
const showSpinner = (blockExecution.isStarting || blockExecution.isStopping) && !!sshParent;
132+
131133
const onBlockOutput = useCallback(async (output: GenericBlockOutput<void>) => {
132134
if (output.stdout) {
133135
xtermRef.current?.write(output.stdout);
@@ -355,14 +357,16 @@ const ScriptBlock = ({
355357
color="danger"
356358
>
357359
<div>
358-
<PlayButton
359-
eventName="runbooks.block.execute"
360-
eventProps={{ type: "script" }}
361-
onPlay={handlePlay}
362-
onStop={blockExecution.cancel}
363-
isRunning={blockExecution.isRunning}
364-
cancellable={true}
365-
/>
360+
<PlayButton
361+
eventName="runbooks.block.execute"
362+
eventProps={{ type: "script" }}
363+
onPlay={handlePlay}
364+
onStop={blockExecution.cancel}
365+
isRunning={blockExecution.isRunning}
366+
cancellable={true}
367+
isLoading={showSpinner}
368+
disabled={showSpinner}
369+
/>
366370
</div>
367371
</Tooltip>
368372

src/lib/blocks/terminal/component.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,8 @@ export const RunBlock = ({
328328

329329
<div className="flex flex-row gap-2 flex-grow w-full" ref={elementRef}>
330330
<PlayButton
331-
isLoading={isLoading}
331+
isLoading={isLoading || ((execution.isStarting || execution.isStopping) && !!sshParent)}
332+
disabled={isLoading || ((execution.isStarting || execution.isStopping) && !!sshParent)}
332333
isRunning={execution.isRunning}
333334
cancellable={true}
334335
onPlay={handlePlay}

src/lib/hooks/useDocumentBridge.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export function useBlockContext(blockId: string, suppressErrors: boolean = false
137137
export function useBlockOutput<T = JsonValue>(
138138
blockId: string,
139139
callback: (output: GenericBlockOutput<T>) => void,
140+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
140141
): void {
141142
const documentBridge = useDocumentBridge();
142143
useEffect(() => {
@@ -175,6 +176,8 @@ export type ExecutionLifecycle = "idle" | "running" | "success" | "error" | "can
175176

176177
export interface ClientExecutionHandle {
177178
isRunning: boolean;
179+
isStarting: boolean;
180+
isStopping: boolean;
178181
isSuccess: boolean;
179182
isError: boolean;
180183
isCancelled: boolean;
@@ -192,6 +195,8 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
192195
const [lifecycle, setLifecycle] = useState<ExecutionLifecycle>("idle");
193196
const [error, setError] = useState<string | null>(null);
194197
const [executionId, setExecutionId] = useState<string | null>(null);
198+
const [isStarting, setIsStarting] = useState(false);
199+
const [isStopping, setIsStopping] = useState(false);
195200

196201
const startExecution = useCallback(async () => {
197202
if (!documentBridge) {
@@ -203,6 +208,7 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
203208
return;
204209
}
205210

211+
setIsStarting(true);
206212
setError(null);
207213
documentBridge.logger.info(
208214
`Starting execution of block ${blockId} in runbook ${documentBridge.runbookId}`,
@@ -212,6 +218,7 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
212218
try {
213219
executionId = await executeBlock(documentBridge.runbookId, blockId);
214220
} catch (error) {
221+
setIsStarting(false);
215222
documentBridge.logger.warn(
216223
`Failed to execute block ${blockId} in runbook ${documentBridge.runbookId} (block should send a BlockOutput with lifecycle set to error)`,
217224
error,
@@ -248,10 +255,16 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
248255
return;
249256
}
250257

258+
setIsStopping(true);
251259
documentBridge.logger.info(
252260
`Cancelling execution of block ${blockId} in runbook ${documentBridge.runbookId} with execution ID: ${executionId}`,
253261
);
254-
await cancelExecution(executionId);
262+
try {
263+
await cancelExecution(executionId);
264+
} catch (error) {
265+
setIsStopping(false);
266+
documentBridge.logger.error("Failed to cancel execution", error);
267+
}
255268
setExecutionId(null);
256269
setError(null);
257270
}, [documentBridge, blockId, executionId, lifecycle]);
@@ -262,19 +275,26 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
262275
setLifecycle("success");
263276
setExecutionId(null);
264277
setError(null);
278+
setIsStarting(false);
279+
setIsStopping(false);
265280
break;
266281
case "cancelled":
267282
setLifecycle("cancelled");
268283
setExecutionId(null);
269284
setError(null);
285+
setIsStarting(false);
286+
setIsStopping(false);
270287
break;
271288
case "error":
272289
setLifecycle("error");
273290
setExecutionId(null);
274291
setError(output.lifecycle?.data.message);
292+
setIsStarting(false);
293+
setIsStopping(false);
275294
break;
276295
case "started":
277296
setLifecycle("running");
297+
setIsStarting(false);
278298
if (output.lifecycle?.data) {
279299
setExecutionId(output.lifecycle.data);
280300
}
@@ -286,6 +306,8 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
286306
setLifecycle("success");
287307
setExecutionId(null);
288308
setError(null);
309+
setIsStarting(false);
310+
setIsStopping(false);
289311
break;
290312

291313
default:
@@ -300,6 +322,8 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
300322

301323
return {
302324
isRunning: lifecycle === "running",
325+
isStarting,
326+
isStopping,
303327
isSuccess: lifecycle === "success",
304328
isError: lifecycle === "error",
305329
isCancelled: lifecycle === "cancelled",
@@ -310,6 +334,8 @@ export function useBlockExecution(blockId: string): ClientExecutionHandle {
310334
setLifecycle("idle");
311335
setError(null);
312336
setExecutionId(null);
337+
setIsStarting(false);
338+
setIsStopping(false);
313339
},
314340
};
315341
}

0 commit comments

Comments
 (0)