diff --git a/frontend/src/components/Detection.jsx b/frontend/src/components/Detection.jsx
new file mode 100644
index 00000000..c09d0c7a
--- /dev/null
+++ b/frontend/src/components/Detection.jsx
@@ -0,0 +1,210 @@
+import React, { useState } from "react";
+import {
+ Container,
+ Box,
+ TextField,
+ Switch,
+ Typography,
+ Button,
+ CircularProgress,
+ Paper,
+} from "@mui/material";
+
+import { toast } from "react-toastify";
+import theme from '../theme.jsx';
+import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
+
+const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isTenzirActive) => {
+
+ if (!isTenzirActive) {
+ toast("connect to siem first for global enable/disable to work");
+ return;
+ }
+
+ const action = folderDisabled ? "enable_folder" : "disable_folder";
+ const url = `${globalUrl}/api/v1/detections/${action}`;
+
+ fetch(url, {
+ method: "PUT",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === true) {
+ if (action === "enable_folder") setFolderDisabled(false);
+ else setFolderDisabled(true);
+ } else {
+ //toast(`failed to disable rule`);
+ }
+ })
+ )
+ .catch((error) => {
+ console.log(`Error in ${action} the rule: `, error);
+ toast(`An error occurred while ${action} the rule`);
+ });
+};
+
+const Detection = (props) => {
+ const { globalUrl, ruleInfo, folderDisabled, setFolderDisabled, isTenzirActive } = props;
+ const [searchQuery, setSearchQuery] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const handleConnectClick = () => {
+ if (!isTenzirActive) {
+ setLoading(true);
+ const url = `${globalUrl}/api/v1/detections/siem/connect`;
+
+ fetch(url, {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === true) {
+ setTimeout(() => {
+ setLoading(false);
+ window.location.reload();
+ }, 15000);
+ } else {
+ setLoading(false);
+ toast("Failed to connect to SIEM");
+ }
+ })
+ )
+ .catch((error) => {
+ setLoading(false);
+ console.log(`Error in connecting to SIEM: `, error);
+ toast("An error occurred while connecting to SIEM");
+ });
+ } else {
+ console.log("Already connected to SIEM");
+ }
+ };
+
+ const filteredRules = ruleInfo?.filter((rule) =>
+ rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ rule.description.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ return (
+
+
+
+
+ Sigma Detection Rules
+
+
+ {loading ? : isTenzirActive ? "Connected to siem" : "Connect to siem"}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+ {/* uploadRef.current.click()}
+ >
+ Upload sigma file
+
+ {
+ uploadFiles(event.target.files);
+ }}
+ /> */}
+
+
+
+ Global disable/enable
+
+
+ handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isTenzirActive)
+ }
+ disabled={!isTenzirActive}
+ />
+
+
+
+ {filteredRules?.length > 0 ?
+ filteredRules.map((card) => {
+ console.log("RULE CARD: ", card);
+
+ return (
+
+ )
+ })
+ : null }
+
+
+
+ );
+};
+
+export default Detection;
diff --git a/frontend/src/components/DetectionExplorer.jsx b/frontend/src/components/DetectionExplorer.jsx
new file mode 100644
index 00000000..c81bee26
--- /dev/null
+++ b/frontend/src/components/DetectionExplorer.jsx
@@ -0,0 +1,399 @@
+import React, { useState, useEffect, } from "react";
+import {
+ Container,
+ Box,
+ TextField,
+ Switch,
+ Typography,
+ Button,
+ CircularProgress,
+ Paper,
+ Divider,
+ IconButton,
+} from "@mui/material";
+
+import {
+ OpenInNew as OpenInNewIcon,
+} from "@mui/icons-material"
+
+import { toast } from "react-toastify";
+import theme from '../theme.jsx';
+import DetectionRuleCard from "../components/DetectionRuleCard.jsx";
+import {
+ green,
+ red,
+ grey,
+} from "../views/AngularWorkflow.jsx"
+
+import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
+
+const handleDirectoryChange = (folderDisabled, setFolderDisabled, globalUrl, isDetectionActive) => {
+ if (!isDetectionActive) {
+ toast.warn("Connect to siem first for global enable/disable to work");
+ return;
+ }
+
+ const action = folderDisabled ? "enable_folder" : "disable_folder";
+ const url = `${globalUrl}/api/v1/detections/${action}`;
+
+ fetch(url, {
+ method: "PUT",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === true) {
+ if (action === "enable_folder") setFolderDisabled(false);
+ else setFolderDisabled(true);
+ } else {
+ //toast(`failed to disable rule`);
+ }
+ })
+ )
+ .catch((error) => {
+ console.log(`Error in ${action} the rule: `, error);
+ toast(`An error occurred while ${action} the rule`);
+ });
+};
+
+const DetectionExplorer = (props) => {
+ const { globalUrl, userdata, ruleInfo, folderDisabled, setFolderDisabled, detectionInfo, importDetectionFromUrl, rulesLoading, isDetectionActive, setIsDetectionActive, ruleMapping, setRuleMapping, } = props;
+ const [searchQuery, setSearchQuery] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const [workflow, setWorkflow] = useState({})
+ const [detectionWorkflowId, setDetectionWorkflowId] = useState("")
+ const [isDetectionValid, setIsDetectionValid] = useState(false)
+ const [availableDetection, setAvailableDetection] = React.useState([]);
+
+ const loadUsecases = () => {
+ const url = `${globalUrl}/api/v1/workflows/usecases`
+ fetch(url, {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson.success === false) {
+ return
+ }
+
+ if (responseJson.length == 0) {
+ return
+ }
+
+ for (var usecaseCategory in responseJson) {
+ const category = responseJson[usecaseCategory]
+ if (!category.name.toLowerCase().includes("respond") && !category.name.toLowerCase().includes("response")) {
+ continue
+ }
+
+ setAvailableDetection(category.list)
+ break
+ }
+ })
+ )
+ .catch((error) => {
+ console.log(`Error in loading usecases: `, error);
+ //toast(`An error occurred while loading usecases`);
+ })
+ }
+
+ const loadWorkflow = (workflowId) => {
+ const url = `${globalUrl}/api/v1/workflows/${workflowId}`
+ fetch(url, {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson.id === workflowId) {
+ setWorkflow(responseJson)
+ } else {
+ toast(`Failed to load workflow ${workflowId}`);
+ }
+ }))
+ .catch((error) => {
+ console.log(`Error in loading workflow ${workflowId}: `, error);
+ toast(`An error occurred while loading workflow ${workflowId}`);
+ })
+ }
+
+ const handleConnectClick = () => {
+ if (detectionWorkflowId !== "") {
+ // FIXME: Show the Usecase UI for how to fix the workflow(s)
+ // Instead loading full workflow and showing it directly? Hmm
+ //toast.warn("Please reload the UI to load the detection status")
+ return
+ }
+
+ if (isDetectionActive) {
+ return
+ }
+
+ if (detectionInfo.category === undefined || detectionInfo.category === null) {
+ toast.warn("Detection category not found. Please try again or contact support@shuffler.io if you think this is a bug.")
+ return
+ }
+
+ setLoading(true);
+ const url = `${globalUrl}/api/v1/detections/${detectionInfo?.category}/connect`;
+
+ fetch(url, {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === true) {
+ setLoading(false)
+
+ if (setIsDetectionActive !== undefined) {
+ setIsDetectionActive(true)
+ }
+
+ if (responseJson.workflow_id !== undefined && responseJson.workflow_id !== null) {
+ setDetectionWorkflowId(responseJson.workflow_id)
+
+ loadWorkflow(responseJson.workflow_id)
+ }
+
+ if (responseJson.workflow_valid !== undefined && responseJson.workflow_valid !== null) {
+ setIsDetectionValid(responseJson.workflow_valid)
+ }
+ } else {
+ if (responseJson.reason !== undefined && responseJson.reason !== null) {
+ toast(responseJson.reason)
+ } else {
+ toast(`Failed to connect to ${detectionInfo?.category}`);
+ }
+
+ if (responseJson.action !== undefined && responseJson.actio !== null && responseJson.action.length > 0) {
+ //if (responseJson.action === "environment_create") {
+ // navigate("/admin?tab=environments")
+ //}
+ }
+
+ setLoading(false);
+ }
+ })
+ )
+ .catch((error) => {
+ setLoading(false);
+ console.log(`Error in connecting to ${detectionInfo?.category}: `, error);
+ toast(`An error occurred while connecting to ${detectionInfo?.category}`);
+ });
+ }
+
+
+ useEffect(() => {
+ loadUsecases()
+ }, [])
+
+ useEffect(() => {
+ handleConnectClick()
+ }, [detectionInfo])
+
+ const filteredRules = ruleInfo === "default" ? [] : ruleInfo?.filter((rule) =>
+ rule.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ rule.description.toLowerCase().includes(searchQuery.toLowerCase())
+ )
+
+ return (
+
+
+
+
+ {detectionInfo?.title} {filteredRules === undefined || filteredRules === null ? null : `(${filteredRules?.length} rules)`}
+
+
+ {workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 ?
+
+
+
+
+
+
{
+ window.open(`/workflows/${workflow.id}`, "_blank")
+ }}
+ >
+
+
+
+
+ :
+ {
+ handleConnectClick()
+ }}
+ disabled={loading} // Disable the button while loading
+ style={{
+ // Red = workflow exists, validation is false
+ // Green = workflow exists, validation is true
+ // Grey = workflow does not exist
+ backgroundColor: detectionWorkflowId === "" ? grey : isDetectionValid ? green : red,
+ }}
+ >
+ {loading ? :
+ detectionWorkflowId === "" ? `Connect to ${detectionInfo?.category}` :
+ isDetectionValid ? `Connected to ${detectionInfo?.category}` : `Fix ${detectionInfo?.category} connection`}
+
+ }
+
+ {filteredRules?.length > 0 ?
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+ Global disable/enable
+
+
+ handleDirectoryChange(folderDisabled, setFolderDisabled, globalUrl, isDetectionActive)
+ }
+ />
+
+
+ : null}
+
+
+
+ {filteredRules?.length > 0 ?
+
+ ruleMapping !== undefined && ruleMapping !== null && ruleMapping.value !== undefined && ruleMapping.value !== null ?
+ filteredRules.map((rule, index) => {
+ return (
+
+
+
+ )
+ })
+ : null
+ :
+
+ {rulesLoading === true ?
+
+
+
+ Downloading rules, please wait...
+
+
+ :
+
+
+ No rules loaded yet
+
+ {
+ if (importDetectionFromUrl !== undefined) {
+ importDetectionFromUrl(true, detectionInfo.download_repo)
+ } else {
+ toast("Import function not found. Please contact support@shuffler.io")
+ }
+ }}
+ >
+ Load Default Rules
+
+
+ }
+
+ }
+
+
+
+ );
+};
+
+export default DetectionExplorer;
diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx
index 257398f8..59d47c0a 100644
--- a/frontend/src/components/EditWorkflow.jsx
+++ b/frontend/src/components/EditWorkflow.jsx
@@ -61,10 +61,11 @@ import {
OpenInNew as OpenInNewIcon,
Add as AddIcon,
Remove as RemoveIcon,
+ EditNote as EditNoteIcon,
} from "@mui/icons-material";
const EditWorkflow = (props) => {
- const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, } = props
+ const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, } = props
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
@@ -82,8 +83,8 @@ const EditWorkflow = (props) => {
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
const [inputMarkdown, setInputMarkdown] = React.useState(workflow.input_markdown !== undefined && workflow.input_markdown !== null ? workflow.input_markdown : "")
- const [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "")
const [scrollDone, setScrollDone] = React.useState(false)
+ const [selectedYieldActions, setSelectedYieldActions] = React.useState(workflow.output_yields !== undefined && workflow.output_yields !== null ? JSON.parse(JSON.stringify(workflow.output_yields)) : [])
const classes = useStyles();
@@ -204,25 +205,28 @@ const EditWorkflow = (props) => {
{newWorkflow ? "New" : "Editing"} workflow
+
{newWorkflow === true ? null :
}
+
Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more
@@ -306,6 +310,8 @@ const EditWorkflow = (props) => {
innerWorkflow.input_questions = validfields
innerWorkflow.input_markdown = inputMarkdown
+ innerWorkflow.output_yields = selectedYieldActions
+
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
@@ -382,16 +388,16 @@ const EditWorkflow = (props) => {
Usecases
selected.join(', ')}
- onChange={(event) => {
- console.log("Changed: ", event)
- }}
- >
+ defaultValue=""
+ id="grouped-select"
+ label="Matching Usecase"
+ multiple
+ value={selectedUsecases}
+ renderValue={(selected) => selected.join(', ')}
+ onChange={(event) => {
+ console.log("Changed: ", event)
+ }}
+ >
None
@@ -614,6 +620,11 @@ const EditWorkflow = (props) => {
+
+ MSSP controls
+
+
+
MSSP Suborg Distribution (beta - contact support@shuffler.io for more info)
@@ -719,11 +730,11 @@ const EditWorkflow = (props) => {
}
-
+ {/* */}
-
+
Git Backup Repository
@@ -875,9 +886,38 @@ const EditWorkflow = (props) => {
-
- Input fields
-
+
+
+
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the Form page for this workflow. If chosen in the User Input node, these will be required fields. Use Semi-Colon ";" to create dropdown options. The first key will be the name shown, and subsequent keys will be the available values.
@@ -1008,6 +1048,10 @@ const EditWorkflow = (props) => {
}}
onChange={(e) => {
+ if (setRealtimeMarkdown !== undefined) {
+ setRealtimeMarkdown(e.target.value)
+ }
+
setInputMarkdown(e.target.value)
workflow.input_markdown = e.target.value
setWorkflow(workflow)
@@ -1015,6 +1059,61 @@ const EditWorkflow = (props) => {
}}
/>
+
+
+
+ Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`})
+
+
+
+ When running this workflow, the output will be shown as a Markdown object by default, with JSON objects being rendered. By adding nodes below, they will be shown while the workflow is running as soon as they get a result. Failing/Skipped nodes are not shown. This makes it possible to track progress for more complex usecases.
+
+
+
+ selected.join(', ')}
+ onChange={(event) => {
+ console.log("Value: ", event.target.value)
+ if (event.target.value.length > 0) {
+ if (event.target.value.includes("none")) {
+ setSelectedYieldActions([])
+ return
+ }
+ }
+
+ const newvalue = event?.target?.value
+ if (newvalue === undefined || newvalue === null) {
+ } else {
+ setSelectedYieldActions(newvalue)
+ }
+ }}
+ >
+
+ None
+
+ {workflow.actions.map((action, actionIndex) => {
+ return (
+
+
+ 0 ? action.large_image : theme.palette.defaultImage} style={{width: 20, height: 20, marginRight: 10, }} />
+
+ {action.label}
+
+ )
+ })}
+
+
+
: null}
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index 6a236161..5754f00e 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -8602,7 +8602,6 @@ const releaseToConnectLabel = "Release to Connect"
if (alledges !== undefined && alledges !== null && alledges.length > 0) {
for (let edgekey in alledges) {
const tmp = alledges[edgekey]
- console.log("TMP: ", tmp, tmp.data.source)
if (tmp.data.source === trigger.id) {
mappedStartnode = tmp.data.target
break
@@ -16079,7 +16078,7 @@ const releaseToConnectLabel = "Release to Connect"
}}
/>
-
Interval (UTC)
+
When to start: {isCloud || selectedTrigger?.environment === "cloud" ? Cron formatting : "every X second"}
- Execution argument:
+ Runtime Argument:
@@ -19303,11 +19302,11 @@ const releaseToConnectLabel = "Release to Connect"
executionData.execution_source === "questions" || executionData.execution_source === "web" || executionData.execution_source === "form" || executionData.execution_source === "forms" ?
- Questions
+ Form
:
executionData.execution_source
@@ -22178,9 +22177,9 @@ const releaseToConnectLabel = "Release to Connect"
const drawerData = originalWorkflow !== undefined && originalWorkflow !== null ?
- Version History (Beta)
+ Version History
-
+
diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx
index 3b2c2e0c..e8f3617d 100644
--- a/frontend/src/views/RunWorkflow.jsx
+++ b/frontend/src/views/RunWorkflow.jsx
@@ -2,6 +2,7 @@
import React, {useState, useEffect} from 'react';
import ReactDOM from "react-dom"
+import ReactJson from "react-json-view-ssr";
import { green, yellow, red, grey} from "./AngularWorkflow.jsx";
import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";
import { useNavigate, Link, useParams } from "react-router-dom";
@@ -36,6 +37,8 @@ import {
import {
Preview as PreviewIcon,
ContentCopy as ContentCopyIcon,
+ ArrowBack as ArrowBackIcon,
+ ArrowForward as ArrowForwardIcon,
} from '@mui/icons-material';
const hrefStyle = {
@@ -71,6 +74,7 @@ const RunWorkflow = (defaultprops) => {
const [foundSourcenode, setFoundSourcenode] = React.useState(undefined);
const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false)
const [sharingOpen, setSharingOpen] = React.useState(false)
+ const [realtimeMarkdown, setRealtimeMarkdown] = React.useState("")
const boxStyle = {
color: "white",
@@ -225,10 +229,13 @@ const RunWorkflow = (defaultprops) => {
id="markdown_wrapper"
escapeHtml={false}
style={{
- maxWidth: "100%", minWidth: "100%",
+ maxWidth: "100%",
+ minWidth: "100%",
+ overflowX: "hidden",
+ overflowY: "auto",
}}
>
- {executionData.result}
+ {executionData.result}
: null}
@@ -376,6 +383,7 @@ const RunWorkflow = (defaultprops) => {
})
}
+
const getWorkflow = (workflow_id, selectedNode) => {
const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
@@ -477,6 +485,23 @@ const RunWorkflow = (defaultprops) => {
}
}
+ if (responseJson.input_markdown !== undefined && responseJson.input_markdown !== null && responseJson.input_markdown.length > 0) {
+ // Look for {{ uuid }} format, and try to run that workflow with their account
+ // This is a hack, but a fun one.
+
+ const uuidRegex = /{{\s*[a-f0-9-]+\s*}}/g
+ const found = responseJson.input_markdown.match(uuidRegex)
+ if (found !== undefined && found !== null && found.length > 0) {
+ for (var foundkey in found) {
+ const uuid = found[foundkey].replace("{{", "").replace("}}", "").trim()
+ // Run the workflow, then replace it.
+ // Only run if logged in
+ console.log("Found UUID: " + uuid)
+ responseJson.input_markdown = responseJson.input_markdown.replace(found[foundkey], "")
+ }
+ }
+ }
+
handleGetOrg(responseJson.org_id)
setWorkflow(responseJson);
})
@@ -651,6 +676,10 @@ const RunWorkflow = (defaultprops) => {
const sourceNode = searchParams.get("source_node")
useEffect(() => {
+ if (!isLoaded) {
+ return
+ }
+
getWorkflow(props.match.params.key, sourceNode)
if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) {
console.log("Get execution: ", execution_id)
@@ -660,7 +689,7 @@ const RunWorkflow = (defaultprops) => {
if (answer !== undefined && answer !== null) {
console.log("Got answer: ", answer)
}
- }, [])
+ }, [isLoaded])
useEffect(() => {
@@ -744,6 +773,7 @@ const RunWorkflow = (defaultprops) => {
}
}
+ var validResults = 0
const basedata =
@@ -772,7 +802,7 @@ const RunWorkflow = (defaultprops) => {
}}
rehypePlugins={[rehypeRaw]}
>
- {workflow.input_markdown}
+ {realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0 ? realtimeMarkdown : workflow.input_markdown}
: null}
@@ -858,7 +888,7 @@ const RunWorkflow = (defaultprops) => {
>
{multiChoiceOptions.map((option, menuIndex) => {
- if (index === 0) {
+ if (menuIndex === 0) {
return null
}
@@ -928,15 +958,17 @@ const RunWorkflow = (defaultprops) => {
}
+
+
{executionRunning ?
- {executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
+ {/*executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
Status: {executionData.status}
- : null}
+ : null*/}
:
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
@@ -994,19 +1026,62 @@ const RunWorkflow = (defaultprops) => {
}
- {/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
-
{
- console.log("Img loaded.")
- setTimeout(() => {
- console.log("Img closing.")
- setButtonClicked("finished")
- }, 1250)
+ {workflow.output_yields !== undefined && workflow.output_yields !== null && workflow.output_yields.length > 0 ?
+
+ {workflow.output_yields.map((yieldItem, index) => {
+ if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) {
+ return null
+ }
- }}
- />
- : null*/}
+ const foundresult = executionData.results.find((result) => {
+ return result.action.id === yieldItem
+ })
+
+ if (foundresult === undefined || foundresult === null) {
+ return null
+ }
+
+ if (foundresult.status === "SKIPPED") {
+ return null
+ }
+
+ const validate = validateJson(foundresult.result)
+ validResults += 1
+ console.log("VAlid: ", validate)
+
+ var appendedDetails = foundresult.result
+ if (validate.valid) {
+ appendedDetails =
{
+ //handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onSelect={(select) => {
+ //HandleJsonCopy(validate.result, select, "exec");
+ }}
+ name={false}
+ />
+ }
+
+ return (
+
+ {foundresult?.action?.label?.replaceAll("_", " ")} - {foundresult.status}:
+
+
+ {appendedDetails}
+
+ )
+ })}
+
+ : null}
{executionInfo}
@@ -1015,6 +1090,8 @@ const RunWorkflow = (defaultprops) => {
{answer !== undefined && answer !== null ? null :
}
+
+
}
@@ -1037,7 +1114,8 @@ const RunWorkflow = (defaultprops) => {
usecases={undefined}
expanded={true}
- scrollTo={"input_markdown"}
+ setRealtimeMarkdown={setRealtimeMarkdown}
+ scrollTo={"form_fill"}
/>
: null}
@@ -1112,20 +1190,46 @@ const RunWorkflow = (defaultprops) => {
color={"secondary"}
style={{marginRight: 10, }}
onClick={() => {
- setEditWorkflowModalOpen(true)
+ navigate(`/workflows`)
}}
>
- Edit Details
+
+ Workflows
+
{
+ window.open(`/workflow/${workflow.id}`, "_blank")
+ }}
+ >
+
+ Workflow
+
+
+
{
setSharingOpen(true)
}}
>
Share Form
+
+
{
+ setEditWorkflowModalOpen(true)
+ }}
+ >
+ Edit Details
+
: null}
diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx
index 0c55a8d7..a17b08ed 100755
--- a/frontend/src/views/Workflows.jsx
+++ b/frontend/src/views/Workflows.jsx
@@ -75,6 +75,7 @@ import {
ArrowRight as ArrowRightIcon,
QueryStats as QueryStatsIcon,
Visibility as VisibilityIcon,
+ EditNote as EditNoteIcon,
} from "@mui/icons-material";
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
@@ -2050,6 +2051,18 @@ const Workflows = (props) => {
{"Edit details"}
+
+
{
+ window.open(`/forms/${data.id}`, "_blank")
+ }}
+ key={"explore forms"}
+ >
+
+ {"Create Form"}
+
+
{
})
: null}
- {data.actions !== undefined && data.actions !== null ? (
-
-
-
-
- {workflowMenuButtons}
-
- ) : null}
+ {data.actions !== undefined && data.actions !== null ? (
+
+
+
+
+ {workflowMenuButtons}
+
+ ) : null}
+ {(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data.input_markdown !== undefined && data.input_markdown !== null && data.input_markdown !== "") ?
+
+
+ {
+ navigate(`/forms/${data.id}`)
+ }}
+ style={{ padding: "0px", color: "#979797" }}
+ >
+
+
+ {workflowMenuButtons}
+
+
+ : null}