Explorar o código

feat(webui): activate a workflow from the editor toolbar

Activation was only reachable from the workflows list, so the editor gave no
way to start a workflow and no indication of whether it was already running.
That is awkward now that the schedule trigger is configured in the editor:
you set the interval there, then have to leave to turn it on.

Adds a toggle between Settings and Save, calling the same activate and
deactivate endpoints the list page uses, so there is one mechanism rather
than a second code path.

The label is the action, Activate or Deactivate, matching the list page's
wording and Play/Pause icons, and the button carries the green treatment from
the list page's status badge while running. State is encoded in colour and
the action in the label, so the control does not have to double as a status
readout that says one thing and does another.
fszontagh hai 1 mes
pai
achega
36012bd77d

+ 28 - 1
webui/src/components/workflow/EditorHeader.tsx

@@ -1,4 +1,4 @@
-import { ArrowLeft, Save, Play, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown, Pencil } from 'lucide-react'
+import { ArrowLeft, Save, Play, Pause, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown, Pencil } from 'lucide-react'
 import { useState, useRef, useEffect, useCallback } from 'react'
 import type { ExecutionDetail } from '../../api/workflows'
 
@@ -18,9 +18,12 @@ interface EditorHeaderProps {
   pinnedExecution: ExecutionDetail | null
   showExecutionPanel: boolean
   hasExecutionData: boolean
+  isActive: boolean
+  isTogglingActive: boolean
   triggers: TriggerNode[]
   onNavigateBack: () => void
   onSave: () => void
+  onToggleActive: () => void
   onExecute: () => void
   onExecuteTrigger: (triggerNodeId: string) => void
   onAddNode: () => void
@@ -42,9 +45,12 @@ export function EditorHeader({
   pinnedExecution,
   showExecutionPanel,
   hasExecutionData,
+  isActive,
+  isTogglingActive,
   triggers,
   onNavigateBack,
   onSave,
+  onToggleActive,
   onExecute,
   onExecuteTrigger,
   onAddNode,
@@ -211,6 +217,27 @@ export function EditorHeader({
           <Cog className="w-4 h-4" />
           Settings
         </button>
+        <button
+          onClick={onToggleActive}
+          disabled={isTogglingActive}
+          title={isActive
+            ? 'Stop running this workflow on its schedule'
+            : 'Run this workflow on its schedule'}
+          className={`flex items-center gap-2 px-3 py-1.5 rounded-lg disabled:opacity-50 ${
+            isActive
+              ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 hover:bg-green-200 dark:hover:bg-green-900/50'
+              : 'bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-slate-600'
+          }`}
+        >
+          {isTogglingActive ? (
+            <Loader2 className="w-4 h-4 animate-spin" />
+          ) : isActive ? (
+            <Pause className="w-4 h-4" />
+          ) : (
+            <Play className="w-4 h-4" />
+          )}
+          {isActive ? 'Deactivate' : 'Activate'}
+        </button>
         <button
           onClick={onSave}
           disabled={!hasChanges || isSaving}

+ 18 - 0
webui/src/pages/WorkflowEditorPage.tsx

@@ -425,6 +425,21 @@ function WorkflowEditorInner() {
     },
   })
 
+  // Activation is a lifecycle action, not part of saving: it registers the
+  // workflow with the scheduler. Uses the same endpoints as the workflows list.
+  const toggleActiveMutation = useMutation({
+    mutationFn: (currentlyActive: boolean) =>
+      currentlyActive ? workflowsApi.deactivate(id!) : workflowsApi.activate(id!),
+    onSuccess: (_data, currentlyActive) => {
+      queryClient.invalidateQueries({ queryKey: ['workflow', id] })
+      queryClient.invalidateQueries({ queryKey: ['workflows'] })
+      showToast('success', currentlyActive ? 'Workflow deactivated' : 'Workflow activated')
+    },
+    onError: (error: any) => {
+      showToast('error', `Could not change activation: ${error.message}`)
+    },
+  })
+
   // Track pending execution ID for early event capture
   const pendingExecIdRef = useRef<string | null>(null)
   const pendingEventsRef = useRef<Array<{ type: string; data: any }>>([])
@@ -2018,9 +2033,12 @@ function WorkflowEditorInner() {
         pinnedExecution={pinnedExecution}
         showExecutionPanel={showExecutionPanel || isViewingExecution || !!pinnedExecution}
         hasExecutionData={!!(effectiveExecutionState.executionId || Object.keys(effectiveExecutionState.nodeStates).length > 0)}
+        isActive={!!workflow?.active}
+        isTogglingActive={toggleActiveMutation.isPending}
         triggers={triggerNodes}
         onNavigateBack={() => navigate(fromGroupId ? `/workflows?group=${encodeURIComponent(fromGroupId)}` : '/workflows')}
         onSave={handleSave}
+        onToggleActive={() => toggleActiveMutation.mutate(!!workflow?.active)}
         onExecute={() => executeWorkflow()}
         onExecuteTrigger={executeTrigger}
         onAddNode={() => setShowNodePicker(true)}