feat(server): add RSS feed fetching functionality and enhance workflow management
- Introduced a new script for fetching RSS feeds, allowing workflows to process and transform feed data using JSONata. - Updated workflow configuration to include a new RSS feed script, enhancing the capabilities of existing workflows. - Enhanced the workflow management API to support enabling and disabling workflows dynamically. - Improved UI components to reflect the new workflow features, including enabling/disabling workflows and displaying their statuses. - Added support for wildcard filtering in workflow queries, improving the flexibility of workflow management.
This commit is contained in:
@@ -25,7 +25,7 @@ export function EventsPage() {
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
className="input input-sm w-full sm:max-w-sm"
|
||||
placeholder="workflow key"
|
||||
placeholder="workflow key (* wildcard)"
|
||||
value={workflow}
|
||||
onChange={(e) => update("workflow", e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { LuArrowLeft, LuSave } from "react-icons/lu";
|
||||
import { LuArrowLeft, LuPause, LuPlay, LuSave } from "react-icons/lu";
|
||||
import { errorMessage } from "../api/client.js";
|
||||
import { useOwners, useSaveWorkflow, useWorkflow } from "../api/hooks.js";
|
||||
import {
|
||||
useOwners,
|
||||
useRunWorkflow,
|
||||
useSaveWorkflow,
|
||||
useSetWorkflowEnabled,
|
||||
useWorkflow,
|
||||
} from "../api/hooks.js";
|
||||
import { CodeEditor } from "../components/CodeEditor.jsx";
|
||||
import { MermaidDiagram } from "../components/MermaidDiagram.jsx";
|
||||
import { workflowToFlowchart } from "../lib/workflow-mermaid.js";
|
||||
@@ -44,6 +50,15 @@ function WorkflowEditorLayout({
|
||||
saveError,
|
||||
saveSuccess,
|
||||
formId,
|
||||
onRun,
|
||||
runPending,
|
||||
runDisabled,
|
||||
runError,
|
||||
onToggleEnabled,
|
||||
enabled,
|
||||
enablePending,
|
||||
enableDisabled,
|
||||
enableError,
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
@@ -54,6 +69,38 @@ function WorkflowEditorLayout({
|
||||
</Link>
|
||||
<h1 className="truncate text-xl font-semibold">{title}</h1>
|
||||
<div className="flex-1" />
|
||||
{onToggleEnabled ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
disabled={enablePending || enableDisabled}
|
||||
onClick={onToggleEnabled}
|
||||
>
|
||||
{enablePending ? (
|
||||
<span className="loading loading-spinner loading-xs" />
|
||||
) : enabled ? (
|
||||
<LuPause className="size-4" />
|
||||
) : (
|
||||
<LuPlay className="size-4" />
|
||||
)}
|
||||
{enabled ? "Disable" : "Enable"}
|
||||
</button>
|
||||
) : null}
|
||||
{onRun ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
disabled={runPending || runDisabled}
|
||||
onClick={onRun}
|
||||
>
|
||||
{runPending ? (
|
||||
<span className="loading loading-spinner loading-xs" />
|
||||
) : (
|
||||
<LuPlay className="size-4" />
|
||||
)}
|
||||
Run
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="submit"
|
||||
form={formId}
|
||||
@@ -68,6 +115,12 @@ function WorkflowEditorLayout({
|
||||
{saveError ? (
|
||||
<p className="text-error text-sm shrink-0">{saveError}</p>
|
||||
) : null}
|
||||
{runError ? (
|
||||
<p className="text-error text-sm shrink-0">{runError}</p>
|
||||
) : null}
|
||||
{enableError ? (
|
||||
<p className="text-error text-sm shrink-0">{enableError}</p>
|
||||
) : null}
|
||||
{saveSuccess ? (
|
||||
<p className="text-success text-sm shrink-0">Workflow saved</p>
|
||||
) : null}
|
||||
@@ -164,15 +217,33 @@ export function WorkflowNewPage() {
|
||||
}
|
||||
|
||||
export function WorkflowEditPage() {
|
||||
const navigate = useNavigate();
|
||||
const { owner: rawOwner, file: rawFile } = useParams();
|
||||
const owner = decodeURIComponent(rawOwner ?? "");
|
||||
const file = decodeURIComponent(rawFile ?? "");
|
||||
const existing = useWorkflow(owner, file);
|
||||
const save = useSaveWorkflow();
|
||||
const run = useRunWorkflow();
|
||||
const setEnabled = useSetWorkflowEnabled();
|
||||
const [content, setContent] = useState("");
|
||||
const [contentReady, setContentReady] = useState(false);
|
||||
const { parsed, parseError, mermaid } = useYamlPreview(content);
|
||||
|
||||
function onRun() {
|
||||
run.mutate(
|
||||
{ owner, file },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
if (data?.runId) navigate(`/events/${data.runId}`);
|
||||
},
|
||||
onError: (err) => {
|
||||
const runId = err?.response?.data?.runId;
|
||||
if (runId) navigate(`/events/${runId}`);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (existing.isLoading) return;
|
||||
if (existing.data?.content != null) {
|
||||
@@ -214,6 +285,21 @@ export function WorkflowEditPage() {
|
||||
saveDisabled={!contentReady}
|
||||
saveError={save.isError ? errorMessage(save.error) : null}
|
||||
saveSuccess={save.isSuccess}
|
||||
onRun={onRun}
|
||||
runPending={run.isPending}
|
||||
runDisabled={!contentReady || Boolean(parseError) || Boolean(existing.data?.loadError)}
|
||||
runError={run.isError && !run.error?.response?.data?.runId ? errorMessage(run.error) : null}
|
||||
onToggleEnabled={() =>
|
||||
setEnabled.mutate({
|
||||
owner,
|
||||
file,
|
||||
enabled: existing.data?.parsed?.enabled === false,
|
||||
})
|
||||
}
|
||||
enabled={existing.data?.parsed?.enabled !== false}
|
||||
enablePending={setEnabled.isPending}
|
||||
enableDisabled={!contentReady || Boolean(existing.data?.parseError)}
|
||||
enableError={setEnabled.isError ? errorMessage(setEnabled.error) : null}
|
||||
>
|
||||
<form id="workflow-edit-form" onSubmit={onSave} className="flex min-h-0 flex-1 flex-col">
|
||||
<WorkflowYamlAndDiagram
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useState } from "react";
|
||||
import { Link, Navigate, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { LuActivity, LuPencil, LuPlay, LuPlus, LuRefreshCw, LuTrash2 } from "react-icons/lu";
|
||||
import { LuActivity, LuPencil, LuPlay, LuPlus, LuRefreshCw, LuTrash2, LuTriangleAlert } from "react-icons/lu";
|
||||
import { errorMessage } from "../api/client.js";
|
||||
import {
|
||||
useDeleteWorkflow,
|
||||
useReregisterWorkflows,
|
||||
useRunWorkflow,
|
||||
useSetWorkflowEnabled,
|
||||
useWorkflows,
|
||||
} from "../api/hooks.js";
|
||||
import { formatTime, WorkflowStatusBadge } from "../lib/format.jsx";
|
||||
@@ -19,6 +20,7 @@ export function WorkflowsPage() {
|
||||
const [runError, setRunError] = useState(null);
|
||||
const del = useDeleteWorkflow();
|
||||
const run = useRunWorkflow();
|
||||
const setEnabled = useSetWorkflowEnabled();
|
||||
const reregister = useReregisterWorkflows();
|
||||
|
||||
if (editParam) {
|
||||
@@ -57,6 +59,10 @@ export function WorkflowsPage() {
|
||||
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
@@ -89,6 +95,9 @@ export function WorkflowsPage() {
|
||||
) : null}
|
||||
|
||||
{runError ? <p className="text-error text-sm">{runError}</p> : null}
|
||||
{setEnabled.isError ? (
|
||||
<p className="text-error text-sm">{errorMessage(setEnabled.error)}</p>
|
||||
) : null}
|
||||
|
||||
{isLoading ? (
|
||||
<span className="loading loading-spinner" />
|
||||
@@ -116,6 +125,14 @@ export function WorkflowsPage() {
|
||||
>
|
||||
{w.name}
|
||||
</Link>
|
||||
{!w.registered ? (
|
||||
<span
|
||||
className="text-warning ml-1 inline-flex align-middle"
|
||||
title="Not listed in registers.yaml"
|
||||
>
|
||||
<LuTriangleAlert className="size-3.5" />
|
||||
</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="font-mono text-xs">{w.owner}</td>
|
||||
<td>
|
||||
@@ -127,6 +144,21 @@ export function WorkflowsPage() {
|
||||
<td className="whitespace-nowrap">{formatTime(w.lastInvokedAt)}</td>
|
||||
<td>{w.invocationCount}</td>
|
||||
<td className="text-right whitespace-nowrap">
|
||||
<label className="inline-flex items-center mr-1" title={w.enabled ? "Disable" : "Enable"}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-xs"
|
||||
checked={Boolean(w.enabled)}
|
||||
disabled={Boolean(w.loadError) || togglingKey === w.key}
|
||||
onChange={() =>
|
||||
setEnabled.mutate({
|
||||
owner: w.owner,
|
||||
file: w.file,
|
||||
enabled: !w.enabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-xs"
|
||||
|
||||
Reference in New Issue
Block a user