import { useState } from "react"; import { Link, Navigate, useNavigate, useSearchParams } from "react-router-dom"; import { LuActivity, LuCopy, LuPencil, LuPlay, LuPlus, LuRefreshCw, LuTrash2, LuTriangleAlert } from "react-icons/lu"; import { errorMessage } from "../api/client.js"; import { useDeleteWorkflow, useDownloadWorkflowBackup, useRestoreWorkflowBackup, useReregisterWorkflows, useRunWorkflow, useSetWorkflowEnabled, useWorkflows, } from "../api/hooks.js"; import { DuplicateWorkflowDialog } from "../components/DuplicateWorkflowDialog.jsx"; import { WorkflowFileIcon } from "../components/WorkflowFileIcon.jsx"; import { formatTime, WorkflowStatusBadge } from "../lib/format.jsx"; import { useNotifications } from "../notifications.jsx"; export function WorkflowsPage() { const navigate = useNavigate(); const [params] = useSearchParams(); const editParam = params.get("edit"); const { data: workflows = [], isLoading } = useWorkflows(); const [confirmDelete, setConfirmDelete] = useState(null); const [duplicateSource, setDuplicateSource] = useState(null); const [runError, setRunError] = useState(null); const del = useDeleteWorkflow(); const run = useRunWorkflow(); const setEnabled = useSetWorkflowEnabled(); const reregister = useReregisterWorkflows(); const backup = useDownloadWorkflowBackup(); const restoreBackup = useRestoreWorkflowBackup(); const { notify } = useNotifications(); const [restoreMode, setRestoreMode] = useState("merge"); if (editParam) { const slash = editParam.indexOf("/"); if (slash !== -1) { const owner = editParam.slice(0, slash); const file = editParam.slice(slash + 1); return ( ); } } function onRun(w) { setRunError(null); run.mutate( { owner: w.owner, file: w.file }, { onSuccess: (data) => { if (data?.runId) navigate(`/events/${data.runId}`); }, onError: (err) => { const runId = err?.response?.data?.runId; if (runId) { navigate(`/events/${runId}`); return; } setRunError(`${w.key}: ${errorMessage(err)}`); }, }, ); } const runningKey = run.isPending && run.variables ? `${run.variables.owner}/${run.variables.file}` : null; const togglingKey = setEnabled.isPending && setEnabled.variables ? `${setEnabled.variables.owner}/${setEnabled.variables.file}` : null; return (

Workflows

Trash Add
{reregister.isError ? (

{errorMessage(reregister.error)}

) : null} {runError ?

{runError}

: null} {setEnabled.isError ? (

{errorMessage(setEnabled.error)}

) : null}
{isLoading ? ( ) : (
{workflows.map((w) => ( ))}
Name Owner Status Triggers Last run Runs
{w.name} {w.file} {!w.registered ? ( ) : null} {w.owner} {formatTime(w.lastInvokedAt)} {w.invocationCount}
)} {duplicateSource ? ( setDuplicateSource(null)} onDuplicated={(data) => { setDuplicateSource(null); navigate( `/workflows/${encodeURIComponent(data.owner)}/${encodeURIComponent(data.file)}/edit`, ); }} /> ) : null} {confirmDelete ? (

Move {confirmDelete.key} to trash?

The workflow is removed from the list but kept in trash for 7 days. Revision history is preserved.

{del.isError ? (

{errorMessage(del.error)}

) : null}
) : null}
); } function triggerLabel(t) { const type = String(t?.type ?? "").toLowerCase(); if (type === "cron") return t.schedule || "cron"; if (type === "http") return t.path || "/"; if (type === "workflow") return ""; return t?.type ?? "—"; } function triggerKind(t) { const type = String(t?.type ?? "").toLowerCase(); if (type === "http") return t.method || "POST"; if (type === "workflow") return "workflow"; return t?.type ?? "—"; } function TriggerList({ triggers }) { if (!triggers?.length) return —; return ( ); }