import React, { useState, useEffect, useCallback } from 'react'; import { Box, Typography, IconButton, CircularProgress, Tooltip } from '@mui/material'; import { CheckCircle, Error, ArrowBack, Close, Cached as CachedIcon, Pause as PauseIcon } from '@mui/icons-material'; import theme from '../theme.jsx'; import ReactJson from "react-json-view-ssr"; import { toast } from 'react-toastify'; import { validateJson } from "../views/Workflows2.jsx"; // import HandleJsonCopy from "./ShuffleCodeEditor1"; const STATUS_CONFIG = { EXECUTING: { color: '#64B5F6', icon: () => , label: 'Executing' }, SUCCESS: { color: '#4CAF50', icon: () => , label: 'Success' }, FINISHED: { color: '#4CAF50', icon: () => , label: 'Finished' }, ABORTED: { color: '#F44336', icon: () => , label: 'Aborted' } }; let to_be_copied = "" const handleReactJsonClipboard = (copy) => { toast("Copied JSON path to clipboard, NOT Path") }; const HandleJsonCopy = (base, copy, base_node_name) => { if (typeof copy.name === "string") { copy.name = copy.name.replaceAll(" ", "_"); } //lol if (typeof base === 'object' || typeof base === 'dict') { base = JSON.stringify(base) } if (base_node_name === "execution_argument" || base_node_name === "Execution Argument") { base_node_name = "exec" } console.log("COPY: ", base_node_name, copy); //var newitem = JSON.parse(base); var newitem = validateJson(base).result to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); for (let copykey in copy.namespace) { if (copy.namespace[copykey].includes("Results for")) { continue; } if (newitem !== undefined && newitem !== null) { newitem = newitem[copy.namespace[copykey]]; if (!isNaN(copy.namespace[copykey])) { to_be_copied += ".#"; } else { to_be_copied += "." + copy.namespace[copykey]; } } } if (newitem !== undefined && newitem !== null) { newitem = newitem[copy.name]; if (!isNaN(copy.name)) { to_be_copied += ".#"; } else { to_be_copied += "." + copy.name; } } to_be_copied.replaceAll(" ", "_"); const elementName = "copy_element_shuffle"; var copyText = document.getElementById(elementName); if (copyText !== null && copyText !== undefined) { console.log("NAVIGATOR: ", navigator); const clipboard = navigator.clipboard; if (clipboard === undefined) { toast("Can only copy over HTTPS (port 3443)"); return; } navigator.clipboard.writeText(to_be_copied); copyText.select(); copyText.setSelectionRange(0, 99999); /* For mobile devices * /* Copy the text inside the text field */ document.execCommand("copy"); toast("Copied JSON path to clipboard.") console.log("COPYING!"); } else { console.log("Couldn't find element ", elementName); } } const ExecuteWorkflow = async (executionData, globalUrl) => { try { const workflowData = executionData.workflow; // Execute workflow with original parameters await fetch(`${globalUrl}/api/v1/workflows/${workflowData.id}/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, credentials: 'include', body: workflowData }).then(response => { window.location.href = `/workflows/${workflowData.id}/code?execution_id=` + response.json().execution_id; }); } catch (error) { console.error('Error re-executing workflow:', error); } }; const ExecutionsList = ({ executions, onSelectExecution, activeExecutionId }) => { return ( {executions.map((execution) => { const status = STATUS_CONFIG[execution.status] || STATUS_CONFIG.ABORTED; return ( onSelectExecution(execution)} sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer', py: 1, px: 2, borderBottom: '1px solid #2A2A2A', backgroundColor: activeExecutionId === execution.execution_id ? 'rgba(255,255,255,0.05)' : 'transparent', '&:hover': { backgroundColor: 'rgba(255,255,255,0.05)' } }} > {status.icon()} {new Date(execution.started_at * 1000).toLocaleString()} {status.label} ); })} ); }; const ExecutionDetail = ({ execution: initialExecution, onBack, globalUrl, onExecutionUpdate, selectedAction, executeWorkflow }) => { const [execution, setExecution] = useState(initialExecution); const [status, setStatus] = useState(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING); const [validResult, setValidResult] = useState("{}") const abortExecution = async () => { try { await fetch(`${globalUrl}/api/v1/workflows/${execution.workflow.id}/executions/${execution.execution_id}/abort`, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }).then((response) => { if (response.ok) { const updatedExecution = { ...execution, status: "ABORTED", }; setExecution(updatedExecution); onExecutionUpdate(updatedExecution); } }); } catch (error) { console.log("Abort error:", error); } }; useEffect(() => { setStatus(STATUS_CONFIG[execution.status] || STATUS_CONFIG.EXECUTING); }, [execution]); const pollExecutionStatus = useCallback(async () => { try { const response = await fetch(`${globalUrl}/api/v1/streams/results`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ execution_id: execution.execution_id, authorization: execution.authorization, }), }); if (response.ok) { const data = await response.json(); const currentStatus = data.results?.[0]?.status || 'EXECUTING'; const updatedExecution = { ...execution, ...data, status: currentStatus }; setExecution(updatedExecution); onExecutionUpdate(updatedExecution); } } catch (error) { console.error('Polling error:', error); } }, [execution, globalUrl, onExecutionUpdate]); useEffect(() => { let pollTimeout; if (execution.status === 'EXECUTING') { pollTimeout = setTimeout(() => pollExecutionStatus(), 3000); } if (execution?.results?.length === 1) { setValidResult(JSON.parse(execution?.results[0]?.result || "{}")) } return () => clearTimeout(pollTimeout); }, [execution.status, pollExecutionStatus]); return ( Execution Details {status.icon()} {status.label} {execution.status === "EXECUTING" && ( )} { ExecuteWorkflow( execution, globalUrl ); }} sx={{ color: theme.palette.primary.main }} > Started at {new Date(execution.started_at * 1000).toLocaleString()} Execution ID {execution.execution_id} Result
                {execution?.status === 'EXECUTING' ? (
                  
                    
                    Executing...
                  
                ) : (
                  execution?.results?.length === 1 ?
                     {
                        handleReactJsonClipboard(copy);
                      }}
                      collapsed={false}
                      displayDataTypes={false}
                      onSelect={(select) => {
                        var basename = "exec"
                        if (selectedAction !== undefined && selectedAction !== null && Object.keys(selectedAction).length !== 0) {
                          basename = selectedAction.label.toLowerCase().replaceAll(" ", "_")
                        }
                        HandleJsonCopy(validResult, select, basename)
                      }}
                      name={"JSON autocompletion"}
                    /> :
                     { }}
                      displayDataTypes={false}
                      name={"JSON autocompletion"}
                    />
                )}
              
); }; const ExecutionPanel = ({ workflow, globalUrl, onClose, currentExecution, mainAction }) => { const [executions, setExecutions] = useState([]); const [selectedExecution, setSelectedExecution] = useState(null); const [loading, setLoading] = useState(true); const handleExecutionUpdate = useCallback((updatedExecution) => { setExecutions(prevExecutions => { const updatedExecutions = [...prevExecutions]; const index = updatedExecutions.findIndex( e => e.execution_id === updatedExecution.execution_id ); if (index !== -1) { updatedExecutions[index] = updatedExecution; } return updatedExecutions; }); }, []); const fetchExecutions = useCallback(async () => { setLoading(true); try { const response = await fetch(`${globalUrl}/api/v2/workflows/${workflow.id}/executions`, { credentials: 'include', }); if (response.ok) { const data = await response.json(); setExecutions(data.executions); const urlParams = new URLSearchParams(window.location.search); const executionId = urlParams.get('execution_id'); if (executionId) { const execution = data.executions.find(e => e.execution_id === executionId); if (execution) { setSelectedExecution(execution); } } } } catch (error) { console.error('Failed to fetch executions:', error); } finally { setLoading(false); } }, [workflow.id, globalUrl]); useEffect(() => { fetchExecutions(); }, [fetchExecutions]); useEffect(() => { if (currentExecution?.execution_id) { setExecutions(prev => { const existingIndex = prev.findIndex(e => e.execution_id === currentExecution.execution_id); if (existingIndex === -1) { return [currentExecution, ...prev]; } const updated = [...prev]; updated[existingIndex] = currentExecution; return updated; }); setSelectedExecution(currentExecution); } }, [currentExecution]); return ( {loading && !executions.length ? ( ) : selectedExecution ? ( { setSelectedExecution(null); const url = new URL(window.location); url.searchParams.delete('execution_id'); window.history.pushState({}, '', url); fetchExecutions(); }} globalUrl={globalUrl} selecteAction={mainAction} onExecutionUpdate={handleExecutionUpdate} /> ) : ( <> Execution History { setSelectedExecution(execution); const url = new URL(window.location); url.searchParams.set('execution_id', execution.execution_id); window.history.pushState({}, '', url); }} activeExecutionId={currentExecution?.execution_id} /> )} ); }; export default ExecutionPanel;