feat: add script dry-run functionality and enhance script management
- Introduced a new dry-run endpoint for executing scripts without saving changes. - Implemented a dedicated ScriptDryRunPage for user interaction with dry-run functionality. - Enhanced ScriptsPage to support navigation to the dry-run feature. - Added LogViewer component for displaying logs during script execution. - Created ScriptEditPage for editing scripts with integrated dry-run capabilities. - Updated workflows to include new scripts for fetching and notifying data. - Improved overall user experience with better navigation and error handling.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { LuArrowLeft, LuPlay, LuSave } from "react-icons/lu";
|
||||
import { errorMessage } from "../api/client.js";
|
||||
import { useSaveScript, useScript } from "../api/hooks.js";
|
||||
import { CodeEditor } from "../components/CodeEditor.jsx";
|
||||
import { NEW_SCRIPT_TEMPLATE, normalizeScriptName } from "../lib/script.js";
|
||||
|
||||
export function ScriptNewPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [name, setName] = useState("");
|
||||
const [content, setContent] = useState(
|
||||
location.state?.content ?? NEW_SCRIPT_TEMPLATE,
|
||||
);
|
||||
const save = useSaveScript();
|
||||
|
||||
function onSave(e) {
|
||||
e.preventDefault();
|
||||
const file = normalizeScriptName(name);
|
||||
if (!file) return;
|
||||
save.mutate(
|
||||
{ name: file, content },
|
||||
{
|
||||
onSuccess: () => navigate(`/scripts/${encodeURIComponent(file)}/edit`),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function openDryRun() {
|
||||
const file = normalizeScriptName(name);
|
||||
if (!file) return;
|
||||
navigate(`/scripts/${encodeURIComponent(file)}/dry-run`, {
|
||||
state: { content },
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-5rem)] min-h-[32rem] flex-col gap-3">
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<Link to="/scripts" className="btn btn-ghost btn-sm btn-square" aria-label="Back">
|
||||
<LuArrowLeft className="size-4" />
|
||||
</Link>
|
||||
<h1 className="text-xl font-semibold">New script</h1>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline btn-sm"
|
||||
disabled={!normalizeScriptName(name)}
|
||||
onClick={openDryRun}
|
||||
>
|
||||
<LuPlay className="size-4" />
|
||||
Dry run
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="script-edit-form"
|
||||
className="btn btn-primary btn-sm"
|
||||
disabled={save.isPending || !normalizeScriptName(name)}
|
||||
>
|
||||
<LuSave className="size-4" />
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="script-edit-form" onSubmit={onSave} className="flex min-h-0 flex-1 flex-col gap-3">
|
||||
<input
|
||||
className="input input-sm w-full max-w-md shrink-0"
|
||||
placeholder="name.js"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<div className="min-h-0 flex-1">
|
||||
<CodeEditor language="javascript" value={content} onChange={setContent} height="100%" />
|
||||
</div>
|
||||
{save.isError ? (
|
||||
<p className="text-error text-sm shrink-0">{errorMessage(save.error)}</p>
|
||||
) : null}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ScriptEditPage() {
|
||||
const { name: rawName } = useParams();
|
||||
const name = decodeURIComponent(rawName ?? "");
|
||||
const navigate = useNavigate();
|
||||
const existing = useScript(name);
|
||||
const save = useSaveScript();
|
||||
const [content, setContent] = useState("");
|
||||
const [contentReady, setContentReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (existing.isLoading) return;
|
||||
if (existing.data?.content != null) {
|
||||
setContent(existing.data.content);
|
||||
setContentReady(true);
|
||||
}
|
||||
}, [existing.data, existing.isLoading]);
|
||||
|
||||
function onSave(e) {
|
||||
e.preventDefault();
|
||||
save.mutate({ name, content });
|
||||
}
|
||||
|
||||
function openDryRun() {
|
||||
navigate(`/scripts/${encodeURIComponent(name)}/dry-run`, {
|
||||
state: { content },
|
||||
});
|
||||
}
|
||||
|
||||
if (existing.isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-[12rem] items-center justify-center">
|
||||
<span className="loading loading-spinner loading-lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (existing.isError) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<Link to="/scripts" className="btn btn-ghost btn-sm">
|
||||
<LuArrowLeft className="size-4" />
|
||||
Back
|
||||
</Link>
|
||||
<p className="text-error">Script not found</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-5rem)] min-h-[32rem] flex-col gap-3">
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<Link to="/scripts" className="btn btn-ghost btn-sm btn-square" aria-label="Back">
|
||||
<LuArrowLeft className="size-4" />
|
||||
</Link>
|
||||
<h1 className="truncate font-mono text-xl font-semibold">{name}</h1>
|
||||
<div className="flex-1" />
|
||||
<button type="button" className="btn btn-outline btn-sm" onClick={openDryRun}>
|
||||
<LuPlay className="size-4" />
|
||||
Dry run
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="script-edit-form"
|
||||
className="btn btn-primary btn-sm"
|
||||
disabled={save.isPending || !contentReady}
|
||||
>
|
||||
<LuSave className="size-4" />
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="script-edit-form" onSubmit={onSave} className="min-h-0 flex-1">
|
||||
<CodeEditor language="javascript" value={content} onChange={setContent} height="100%" />
|
||||
</form>
|
||||
|
||||
{save.isError ? (
|
||||
<p className="text-error text-sm shrink-0">{errorMessage(save.error)}</p>
|
||||
) : null}
|
||||
{save.isSuccess ? (
|
||||
<p className="text-success text-sm shrink-0">Script saved</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user