From 24317c776d313c37aa4f1b025ce00cf834acd51d Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 12 Nov 2024 10:16:05 +0100 Subject: [PATCH] Biiig changes for 2.0 :)) --- frontend/src/components/AppFramework.jsx | 6 +- frontend/src/components/Appsearch.jsx | 3 - .../src/components/AuthenticationItem.jsx | 2 +- .../src/components/AuthenticationNormal.jsx | 4 +- .../src/components/AuthenticationWindow.jsx | 6 +- frontend/src/components/Billing.jsx | 10 +- frontend/src/components/BillingStats.jsx | 2 +- frontend/src/components/ConfigureWorkflow.jsx | 23 +- frontend/src/components/Detection.jsx | 2 +- frontend/src/components/DetectionExplorer.jsx | 64 +- frontend/src/components/DetectionRuleCard.jsx | 6 +- frontend/src/components/EditWorkflow.jsx | 48 +- frontend/src/components/ExploreWorkflow.jsx | 4 +- .../FixWorkflowValidationErrors.jsx | 6 +- frontend/src/components/Header.jsx | 8 +- frontend/src/components/LicencePopup.jsx | 10 +- frontend/src/components/NewHeader.jsx | 10 +- frontend/src/components/Newsletter.jsx | 3 +- frontend/src/components/Oauth2Auth.jsx | 20 +- frontend/src/components/OrgHeaderexpanded.jsx | 6 +- frontend/src/components/PaperComponent.jsx | 6 +- frontend/src/components/ParsedAction.jsx | 72 +- frontend/src/components/Priorities.jsx | 2 +- frontend/src/components/Priority.jsx | 6 +- frontend/src/components/RecentWorkflow.jsx | 22 +- frontend/src/components/RuntimeDebugger.jsx | 48 +- frontend/src/components/SearchData.jsx | 9 +- frontend/src/components/Searchfield.jsx | 6 +- .../src/components/ShuffleCodeEditor1.jsx | 119 +- .../src/components/SuggestedWorkflows.jsx | 6 +- frontend/src/components/UsecaseSearch.jsx | 10 +- frontend/src/components/WelcomeForm2.jsx | 2 +- .../src/components/WorkflowTemplatePopup.jsx | 2 +- .../src/components/WorkflowTemplatePopup2.jsx | 2 +- .../components/WorkflowValidationTimeline.jsx | 2 +- frontend/src/context/contextApi.jsx | 22 +- frontend/src/defaultCytoscapeStyle.jsx | 4 +- frontend/src/theme.jsx | 1 - frontend/src/views/Admin.jsx | 1058 ++++---- frontend/src/views/AngularWorkflow.jsx | 2164 ++++++----------- frontend/src/views/AppCreator.jsx | 8 +- frontend/src/views/Apps.jsx | 77 +- frontend/src/views/DashboardViews.jsx | 6 +- frontend/src/views/Docs.jsx | 66 +- frontend/src/views/GettingStarted.jsx | 2 +- frontend/src/views/RunWorkflow.jsx | 202 +- frontend/src/views/Search.jsx | 16 +- frontend/src/views/SetAuthentication.jsx | 2 +- frontend/src/views/SettingsPage.jsx | 10 +- frontend/src/views/Usecases.jsx | 16 +- frontend/src/views/Welcome.jsx | 12 +- frontend/src/views/Workflows.jsx | 233 +- 52 files changed, 2191 insertions(+), 2265 deletions(-) diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index 87fe533e..c65cf1cf 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -1972,14 +1972,14 @@ const AppFramework = (props) => { {data.name}
-
+
{parsedLeftImage} {parsedLeftText}
-
+
{svgIcon}
-
+
{parsedRightImage} {parsedRightText}
diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index b5871f2c..dedacfe6 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -27,9 +27,6 @@ const Appsearch = props => { const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true"); const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs - //const theme = useTheme(); - //const [apps, setApps] = React.useState([]); - //const [filteredApps, setFilteredApps] = React.useState([]); const [formMail, setFormMail] = React.useState(""); const [message, setMessage] = React.useState(""); const [formMessage, setFormMessage] = React.useState(""); diff --git a/frontend/src/components/AuthenticationItem.jsx b/frontend/src/components/AuthenticationItem.jsx index eae08e69..175f3510 100644 --- a/frontend/src/components/AuthenticationItem.jsx +++ b/frontend/src/components/AuthenticationItem.jsx @@ -152,7 +152,7 @@ const AuthenticationItem = (props) => { src={data.app.large_image} style={{ maxWidth: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} /> style={{ minWidth: 75, maxWidth: 75 }} diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx index de0f5cfe..96569b41 100644 --- a/frontend/src/components/AuthenticationNormal.jsx +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -222,7 +222,7 @@ const AuthenticationData = (props) => { { { { const authenticationButtons = } + + {detectionInfo?.category === "SIGMA" || detectionInfo?.category === "SIEM" ? + + + 0 ? green : red}} /> + + + : null} +
+ {filteredRules?.length > 0 ? { - const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, } = props + const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, setRealtimeMarkdown, boxWidth, setBoxWidth, } = props const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove @@ -82,12 +83,19 @@ const EditWorkflow = (props) => { const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) 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 [inputMarkdown, setInputMarkdown] = React.useState(workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null ? workflow?.form_control?.input_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 [selectedYieldActions, setSelectedYieldActions] = React.useState(workflow?.form_control?.output_yields !== undefined && workflow?.form_control?.output_yields !== null ? JSON.parse(JSON.stringify(workflow?.form_control?.output_yields)) : []) + const [formWidth, setFormWidth] = React.useState(boxWidth === undefined || boxWidth === null ? 500 : boxWidth) const classes = useStyles(); + useEffect(() => { + if (setBoxWidth !== undefined && boxWidth !== formWidth) { + setBoxWidth(formWidth) + } + }, [formWidth]) + if (scrollTo !== undefined && scrollTo !== null && scrollTo.length > 0 && scrollDone === false) { setTimeout(() => { const foundScroll = document.getElementById(scrollTo) @@ -308,9 +316,14 @@ const EditWorkflow = (props) => { } innerWorkflow.input_questions = validfields - innerWorkflow.input_markdown = inputMarkdown - innerWorkflow.output_yields = selectedYieldActions + if (innerWorkflow.form_control === undefined || innerWorkflow.form_control === null) { + innerWorkflow.form_control = {} + } + + innerWorkflow.form_control.input_markdown = inputMarkdown + innerWorkflow.form_control.output_yields = selectedYieldActions + innerWorkflow.form_control.form_width = formWidth innerWorkflow.name = name innerWorkflow.description = description @@ -1049,13 +1062,34 @@ const EditWorkflow = (props) => { } setInputMarkdown(e.target.value) - workflow.input_markdown = e.target.value + workflow.form_control.input_markdown = e.target.value setWorkflow(workflow) setUpdate(Math.random()) }} />
+
+ + Form Size + + + Control the width of the form. It will grow vertically as needed. + + { + setFormWidth(value) + }} + /> +
+
Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`}) @@ -1117,7 +1151,7 @@ const EditWorkflow = (props) => {
+ ) : null*/} + {workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > 0 ? ( @@ -2201,7 +2195,7 @@ const ParsedAction = (props) => { backgroundColor: theme.palette.inputColor, color: "white", height: "50px", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} > { style={{ backgroundColor: theme.palette.inputColor, height: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { // Workaround with event lol @@ -2476,7 +2470,7 @@ const ParsedAction = (props) => { variant="body1" style={{ backgroundColor: theme.palette.inputColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} label={isIntegration ? "Choose a category" : "Find Actions"} variant="outlined" @@ -2496,7 +2490,7 @@ const ParsedAction = (props) => { value={selectedAction.name} fullWidth onChange={setNewSelectedAction} - style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, borderRadius: theme.palette.borderRadius,}} + style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, borderRadius: theme.palette?.borderRadius,}} SelectDisplayProps={{ style: { marginLeft: 10, @@ -2724,7 +2718,7 @@ const ParsedAction = (props) => { style={{ backgroundColor: theme.palette.inputColor, height: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { console.log("SELECT: ", event, newValue) @@ -2786,7 +2780,7 @@ const ParsedAction = (props) => { {
{ if (selectedAction.parameters === undefined || selectedAction.parameters === null || selectedAction.parameters.length !== selectedActionParameters.length) { //selectedAction.parameters = selectedActionParameters - console.log("PARAM BUG: ", selectedAction) + console.log("PARAM BUG - length change(?): ", selectedAction) } //!selectedAction.auth_not_required && @@ -3008,7 +3002,7 @@ const ParsedAction = (props) => { marginTop: 50, border: "1px solid rgba(255,255,255,0.7)", borderTop: "1px solid rgba(255,255,255,0.7)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, alignItems: "center", textAlign: "center", }} @@ -3284,11 +3278,13 @@ const ParsedAction = (props) => { }} > { "field_number": count, "actionlist": actionlist, "field_id": clickedFieldId, + + "example": selectedActionParameters[count].example, }) }} /> @@ -3646,7 +3644,7 @@ const ParsedAction = (props) => { { backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} > {parsedoptions.map( @@ -4307,7 +4305,7 @@ const ParsedAction = (props) => { color: "white", height: 50, marginTop: 2, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} onChange={(e) => { console.log("SELECT ONCHANGE DONE") diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index e66afe8f..b65aef93 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -286,7 +286,7 @@ const Priorities = (props) => { borderBottom: "1px solid rgba(255,255,255,0.4)", marginBottom: 20, border: highlighted ? "2px solid #f85a3e" : null, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} >
diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 9294967c..4d37142f 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -121,7 +121,7 @@ const Priority = (props) => { const srcSize = realignedSrc ? 35 : 30 const dstSize = realignedDst ? 35 : 30 return ( -
+
{priority.type === "usecase" || priority.type == "apps" ? : null} @@ -131,7 +131,7 @@ const Priority = (props) => { {priority.type === "usecase" && priority.description.includes("&") ? - {priority.name} + {priority.name} {newdescription.split("&")[0]} @@ -139,7 +139,7 @@ const Priority = (props) => { {newdescription.split("&").length > 3 ? - {priority.name+"2"} + {priority.name+"2"} {newdescription.split("&")[2]} diff --git a/frontend/src/components/RecentWorkflow.jsx b/frontend/src/components/RecentWorkflow.jsx index 3bd804b4..f61492e2 100644 --- a/frontend/src/components/RecentWorkflow.jsx +++ b/frontend/src/components/RecentWorkflow.jsx @@ -1,5 +1,6 @@ import React from "react" +import { Link } from "react-router-dom"; import { Avatar, Box, @@ -20,7 +21,6 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow const navigate = useNavigate(); const [hovered, setHovered] = React.useState(false) - if (workflow === undefined || workflow === null) { console.log("No workflow") return null @@ -39,12 +39,11 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow // Check if workflow.input_markdown has an image in it // If it does, show it as the main thing - // var relevantImageUrl = "" - if (workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown !== "") { + if (workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null && workflow?.form_control?.input_markdown !== "") { // Look for tag or ![alt](src) markdown // html > markdown - const imgTag = workflow.input_markdown.match(/]+>/g) + const imgTag = workflow?.form_control?.input_markdown.match(/]+>/g) if (imgTag !== null) { const src = imgTag[0].match(/src="([^"]+)"/) @@ -52,7 +51,7 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow relevantImageUrl = src[1] } } else { - const markdownTag = workflow.input_markdown.match(/!\[.*\]\(.*\)/g) + const markdownTag = workflow?.form_control?.input_markdown.match(/!\[.*\]\(.*\)/g) if (markdownTag !== null) { const src = markdownTag[0].match(/\(([^)]+)\)/) @@ -68,15 +67,13 @@ const RecentWorkflow = ({ workflow, onclickHandler, leftNavOpen, currentWorkflow onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} > + +
) } diff --git a/frontend/src/components/RuntimeDebugger.jsx b/frontend/src/components/RuntimeDebugger.jsx index 9a91eb3e..83345bbc 100644 --- a/frontend/src/components/RuntimeDebugger.jsx +++ b/frontend/src/components/RuntimeDebugger.jsx @@ -35,6 +35,7 @@ import { PlayArrow as PlayArrowIcon, Insights as InsightsIcon, Replay as ReplayIcon, + EditNote as EditNoteIcon, } from '@mui/icons-material'; import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid' @@ -76,6 +77,10 @@ const RuntimeDebugger = (props) => { {"id": "", "name": "All Workflows",} ]) + if (document != undefined) { + document.title = "Workflow Run Debugger" + } + // Shitty workflow search on purpose :) const handleWorkflowUsageCount = (workflows) => { if (workflows === undefined || workflows === null || workflows.length === 0) { @@ -219,7 +224,7 @@ const RuntimeDebugger = (props) => { } - const getAvailableWorkflows = () => { + const getAvailableWorkflows = (workflowId) => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", headers: { @@ -240,6 +245,15 @@ const RuntimeDebugger = (props) => { var foundWorkflows = [{"id": "", "name": "All Workflows",}] foundWorkflows.push(...responseJson) setWorkflows(foundWorkflows) + + if (workflowId !== undefined && workflowId !== null && workflowId !== "" && workflowId.length === 36) { + for (var key in responseJson) { + if (responseJson[key].id === workflowId) { + setWorkflow(responseJson[key]) + break + } + } + } } }) .catch((error) => { @@ -248,7 +262,6 @@ const RuntimeDebugger = (props) => { } useEffect(() => { - getAvailableWorkflows() // Find workflow_id in url query const urlParams = new URLSearchParams(window.location.search); @@ -265,6 +278,8 @@ const RuntimeDebugger = (props) => { } } + getAvailableWorkflows(workflowId) + const foundStatus = urlParams.get('status'); if (foundStatus !== undefined && foundStatus !== null && foundStatus !== "") { setStatus(foundStatus) @@ -314,15 +329,17 @@ const RuntimeDebugger = (props) => { var source = params.row.execution_source if (source === "schedule") { - foundSource = schedule + foundSource = schedule } else if (source === "webhook") { - foundSource = webhook + foundSource = webhook } else if (source === "subflow" || source.length === 36) { - foundSource = subflow + foundSource = subflow source = "subflow" } else if (source === "rerun" || source.length === 36) { foundSource = source = "rerun of a previous run" + } else if (source === "form") { + foundSource = } else { source = "manual" } @@ -888,13 +905,13 @@ const RuntimeDebugger = (props) => { {userdata.support === true ? : null}
@@ -942,11 +959,8 @@ const RuntimeDebugger = (props) => { }, }} getOptionLabel={(option) => { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null + if (option === undefined || option === null || + option.name === undefined || option.name === null ) { return "No Workflow Selected"; } @@ -961,7 +975,7 @@ const RuntimeDebugger = (props) => { style={{ backgroundColor: theme.palette.inputColor, height: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, marginTop: 5, marginLeft: 5, }} @@ -995,13 +1009,13 @@ const RuntimeDebugger = (props) => { {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} + {data.name} : null} Choose {data.name} - } placement="bottom"> + }> { { - const { serverside, globalUrl, userdata, searchBarModalOpen, setSearchBarModalOpen } = props + const { serverside, globalUrl, userdata } = props let navigate = useNavigate(); + const { searchBarModalOpen, setSearchBarModalOpen } = useContext(Context); const borderRadius = 3 const node = useRef() const [searchOpen, setSearchOpen] = useState(false) @@ -547,8 +550,6 @@ const SearchData = props => { e.preventDefault() e.stopPropagation() - console.log("OBJECT CHANGE: ", hit.objectID) - // This does nothing rofl if (userdata.active_apps === undefined || userdata.active_apps === null) { activateApp(hit.name, hit.objectID, "activate") diff --git a/frontend/src/components/Searchfield.jsx b/frontend/src/components/Searchfield.jsx index b3a89011..ec257cc6 100644 --- a/frontend/src/components/Searchfield.jsx +++ b/frontend/src/components/Searchfield.jsx @@ -4,7 +4,7 @@ import theme from '../theme.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; import SearchBox from "../components/SearchData.jsx"; -import { Context } from '../context/contextApi.jsx'; +import { Context } from '../context/ContextApi.jsx'; import { Chip, @@ -101,7 +101,7 @@ const SearchField = props => {
: null} - + @@ -131,7 +131,7 @@ const SearchField = props => {
{modalView} { setActiveDialog, fieldname, contentLoading, + editorData, + + setAiQueryModalOpen, } = props const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); @@ -163,6 +168,11 @@ const CodeEditor = (props) => { setMenuPosition(null); } + useEffect(() => { + highlight_variables(localcodedata) + expectedOutput(localcodedata) + }, [localcodedata]) + let navigate = useNavigate(); useEffect(() => { @@ -811,11 +821,7 @@ const CodeEditor = (props) => { } const handleItemClick = (values) => { - if ( - values === undefined || - values === null || - values.length === 0 - ) { + if (values === undefined || values === null || values.length === 0) { return; } @@ -830,7 +836,11 @@ const CodeEditor = (props) => { toComplete += values[key].autocomplete; } - setlocalcodedata(localcodedata+toComplete) + + handleClick({ + "value": toComplete + }) + //setlocalcodedata(localcodedata+toComplete) setMenuPosition(null) } @@ -839,10 +849,37 @@ const CodeEditor = (props) => { return } - if (!item.value.includes("{%") && !item.value.includes("{{")) { - setlocalcodedata(localcodedata+" | "+item.value+" }}") - } else { - setlocalcodedata(localcodedata+item.value) + // Injects it in the right spot instead of random + var edited = false + if (currentCharacter !== undefined && currentCharacter !== null && currentCharacter !== -1 && currentLine !== undefined && currentLine !== null && currentLine !== -1) { + // Input at the right spot + var codedatasplit = localcodedata.split('\n') + if (codedatasplit.length > currentLine) { + var currentLineData = codedatasplit[currentLine] + + // Remove newlines from item.value + if (item.value.includes("% python %")) { + item.value = item.value.replaceAll("\n", ";") + item.value = item.value.replaceAll("python %};", "python %}") + } else { + item.value = item.value.replaceAll("\n", "") + } + + currentLineData = currentLineData.slice(0, currentCharacter) + item.value + currentLineData.slice(currentCharacter) + codedatasplit[currentLine] = currentLineData + + setlocalcodedata(codedatasplit.join('\n')) + + edited = true + } + } + + if (edited === false) { + if (!item.value.includes("{%") && !item.value.includes("{{")) { + setlocalcodedata(localcodedata+" | "+item.value+" }}") + } else { + setlocalcodedata(localcodedata+item.value) + } } setAnchorEl(null) @@ -946,14 +983,19 @@ const CodeEditor = (props) => { // Define a custom completer for the Ace Editor - const customVariables = availableVariables const customCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { - callback(null, customVariables.map((variable) => ({ - caption: variable, - value: variable, - meta: 'custom', - }))); + console.log("CUSTOM COMPLETER: ", prefix) + + callback(null, availableVariables.map((variable) => { + console.log("CUSTOM VAR: ", variable) + + return ({ + caption: variable, + value: variable, + meta: 'custom', + }) + })) } } @@ -1338,6 +1380,8 @@ const CodeEditor = (props) => { onClick={() => { console.log("CLICKED: ", innerdata); console.log(innerdata.example) + + //const handleClick = (item) => { handleItemClick([innerdata]); }} > @@ -1482,14 +1526,37 @@ const CodeEditor = (props) => { width: 50, marginLeft: 100, }} - disabled={isAiLoading} + disabled={editorData === undefined || editorData.example === undefined || editorData.example === null || editorData.example.length === 0} onClick={() => { - autoFormat(localcodedata) + setlocalcodedata(editorData.example) + }} + color="secondary" + > + + + + + { + if (setAiQueryModalOpen !== undefined) { + setAiQueryModalOpen(true) + } else { + autoFormat(localcodedata) + } }} > {isAiLoading ? @@ -1504,7 +1571,7 @@ const CodeEditor = (props) => { }
{ }}> {(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? ( { highlight_variables(localcodedata) }} onCursorChange={(cursorPosition, editor, value) => { - setCurrentCharacter(cursorPosition.column) - setCurrentLine(cursorPosition.row) + setCurrentCharacter(cursorPosition.cursor.column) + setCurrentLine(cursorPosition.cursor.row) findIndex(cursorPosition.row, cursorPosition.column) - //highlight_variables(localcodedata) - //console.log("VALUE CURSOR: ", value) }} onChange={(value, editor) => { // setlocalcodedata(value) @@ -1666,7 +1733,7 @@ const CodeEditor = (props) => { padding: 10, marginTop: -2, border: `2px solid ${theme.palette.inputColor}`, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, maxHeight: 450, minHeight: 450, overflow: "auto", diff --git a/frontend/src/components/SuggestedWorkflows.jsx b/frontend/src/components/SuggestedWorkflows.jsx index 1f01529a..7780b1a9 100644 --- a/frontend/src/components/SuggestedWorkflows.jsx +++ b/frontend/src/components/SuggestedWorkflows.jsx @@ -102,7 +102,7 @@ const SuggestedWorkflows = (props) => { placement="top" style={{ zIndex: 10011 }} > -
{ +
{ setHovering(true) }} onMouseOut={() => { setHovering(false) @@ -140,7 +140,7 @@ const SuggestedWorkflows = (props) => { // return ( - + 0 && usecaseSearchType.length > 0} onClose={() => { @@ -193,7 +193,7 @@ const SuggestedWorkflows = (props) => { apps={apps} /> -
+
Suggested Workflows ({finishedUsecases.length}/{usecaseSuggestions.length}) diff --git a/frontend/src/components/UsecaseSearch.jsx b/frontend/src/components/UsecaseSearch.jsx index 19aa2e53..2460876d 100644 --- a/frontend/src/components/UsecaseSearch.jsx +++ b/frontend/src/components/UsecaseSearch.jsx @@ -594,7 +594,7 @@ const UsecaseSearch = (props) => { width: 30, height: 30, border: "2px solid rgba(255,255,255,0.6)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, maxHeight: 30, maxWidth: 30, overflow: "hidden", @@ -616,7 +616,7 @@ const UsecaseSearch = (props) => { width: 30, height: 30, border: "2px solid rgba(255,255,255,0.6)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, maxWidth: 30, maxHeight: 30, overflow: "hidden", @@ -1266,9 +1266,11 @@ const UsecaseSearch = (props) => { .then((responseJson) => { if (responseJson.success === false) { var msgString = "Failed to activate the app" + if (responseJson.reason !== undefined) { msgString += ": " + responseJson.reason } + toast(msgString) } else { //toast("App activated for your organization! Refresh the page to use the app.") @@ -1388,7 +1390,7 @@ const UsecaseSearch = (props) => { {startText} -
+
{selectionOpen === true ? { // {defaultSearch}: {allusecases[usecaseIndex].name} //console.log("UseCase: ", usecases) return ( -
+
{configureWorkflowModal} {authenticationModal} {showTitle !== false && defaultSearch !== undefined ? diff --git a/frontend/src/components/WelcomeForm2.jsx b/frontend/src/components/WelcomeForm2.jsx index 8dc43d5f..3e11cfc2 100644 --- a/frontend/src/components/WelcomeForm2.jsx +++ b/frontend/src/components/WelcomeForm2.jsx @@ -648,7 +648,7 @@ const WelcomeForm = (props) => {
-
+
{ {/*errorMessage === "" && configurationFinished === true && workflow.id !== undefined && workflowLoading === false ? { // Open in new tab window.open("/workflows/" + workflow.id, "_blank") diff --git a/frontend/src/components/WorkflowTemplatePopup2.jsx b/frontend/src/components/WorkflowTemplatePopup2.jsx index ddd0a47f..2e666637 100644 --- a/frontend/src/components/WorkflowTemplatePopup2.jsx +++ b/frontend/src/components/WorkflowTemplatePopup2.jsx @@ -759,7 +759,7 @@ const WorkflowTemplatePopup = (props) => { const borderStyle = isHomePage ? null : isHovered && isActive ? errorMessage !== "" ? "1px solid red" : `2px solid ${theme.palette.green}` : isHovered ? `1px solid ${highlightColor}` : "1px solid rgba(33, 33, 33, 1)" return ( -
diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx index 5e20c8d9..5ef7ba70 100644 --- a/frontend/src/components/WorkflowValidationTimeline.jsx +++ b/frontend/src/components/WorkflowValidationTimeline.jsx @@ -319,7 +319,7 @@ const WorkflowValidationTimeline = (props) => {
{ @@ -8,6 +6,21 @@ export const AppContext =(props) => { // Left side bar global states const [searchBarModalOpen, setSearchBarModalOpen] = useState(false); const [leftSideBarOpenByClick, setLeftSideBarOpenByClick] = useState(false); + const [windowWidth, setWindowWidth] = useState(window.innerWidth); + + + //Calculate window width + useEffect(() => { + const handleResize = () => { + setWindowWidth(window?.innerWidth); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); return ( @@ -15,7 +28,8 @@ export const AppContext =(props) => { searchBarModalOpen, setSearchBarModalOpen, leftSideBarOpenByClick, - setLeftSideBarOpenByClick + setLeftSideBarOpenByClick, + windowWidth }}> {props.children} diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index 101d8c44..a070c788 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -263,7 +263,9 @@ const data = [ { selector: "node[?isStartNode]", css: { - shape: "ellipse", + shape: function(element) { + return "ellipse" + }, "border-color": "#80deea", width: "80px", height: "80px", diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index d276c446..54e7a790 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -1,7 +1,6 @@ import React from "react"; import { createTheme, adaptV4Theme } from "@mui/material/styles"; -//const theme = createTheme({ const theme = createTheme(adaptV4Theme({ palette: { theme: "dark", diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index ed520498..15ef65bc 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -6,6 +6,7 @@ import { makeStyles } from "@mui/styles"; import { useNavigate, Link } from "react-router-dom"; import countries from "../components/Countries.jsx"; import CacheView from "../components/CacheView.jsx"; +import { CopyToClipboard } from "../views/Docs.jsx"; import { FormControl, @@ -77,6 +78,9 @@ import { Flag as FlagIcon, FmdGood as FmdGoodIcon, Warning as WarningIcon, + + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, } from "@mui/icons-material"; //import { useAlert @@ -215,6 +219,13 @@ const Admin = (props) => { const [pipelines, setPipelines] = React.useState([]); const [, forceUpdate] = React.useState(); const [MFARequired, setMFARequired] = React.useState(selectedOrganization.mfa_required === undefined ? false : selectedOrganization.mfa_required); + const [listItemExpanded, setListItemExpanded] = React.useState(-1); + const [installationTab, setInstallationTab] = React.useState(0); + const [commandController, setCommandController] = React.useState({ + pipelines: false, + proxies: false, + }) + const [, setUpdate] = React.useState(0); useEffect(() => { if (selectedOrganization.mfa_required !== undefined) { @@ -234,6 +245,13 @@ const Admin = (props) => { setTimeout(() => { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); + + const foundOrgID = params["org_id"] + if (foundOrgID !== null && foundOrgID !== undefined) { + handleClickChangeOrg(foundOrgID) + } + + const foundTab = params["admin_tab"] if (foundTab !== null && foundTab !== undefined) { if (adminTab === 3) { @@ -862,7 +880,7 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); - // setPipelines(responseJson.pipelines || []); + setPipelines(responseJson.pipelines || []); }) .catch((error) => { // toast(error.toString()); @@ -1048,14 +1066,14 @@ If you're interested, please let me know a time that works for you, or set up a const data = { name: pipeline.name, + id: pipeline.id, type: state, + command: pipeline.definition, environment: pipeline.environment, - workflow_id: pipeline.workflow_id, - trigger_id: pipeline.trigger_id, }; if (state === "start") toast("starting the pipeline"); - else toast("stopping the pipeline"); + else toast.info("Stopping the pipeline. This may take a few minutes to propagate.") const url = `${globalUrl}/api/v1/triggers/pipeline`; fetch(url, { @@ -1077,10 +1095,26 @@ If you're interested, please let me know a time that works for you, or set up a }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to update the pipeline: " + responseJson.reason); + toast.error("Failed to update the pipeline: " + responseJson.reason); } else { - if (state === "start") toast("Successfully created pipeline"); - else toast("Sucessfully stopped the pipeline"); + setTimeout(() => { + handleGetAllTriggers() + }, 5000) + + setTimeout(() => { + handleGetAllTriggers() + }, 20000) + + setTimeout(() => { + handleGetAllTriggers() + }, 120000) + /* + if (state === "start") { + toast("Successfully created pipeline"); + } else { + toast("Sucessfully stopped the pipeline"); + } + */ } }) .catch((error) => { @@ -1255,10 +1289,10 @@ If you're interested, please let me know a time that works for you, or set up a } selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync; - setSelectedOrganization(selectedOrganization); - setCloudSyncApikey(""); + setSelectedOrganization(selectedOrganization) + setCloudSyncApikey("") - handleGetOrg(userdata.active_org.id); + handleGetOrg(userdata.active_org.id) } }) .catch((error) => { @@ -1510,23 +1544,17 @@ If you're interested, please let me know a time that works for you, or set up a }; const handleGetOrg = (orgId) => { - if ( - serverside !== true && - window.location.search !== undefined && - window.location.search !== null - ) { + if (serverside !== true && window.location.search !== undefined && window.location.search !== null) { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); - const foundorgid = params["org_id"]; - if (foundorgid !== undefined && foundorgid !== null) { - orgId = foundorgid; + const foundorgid = params["org_id"] + if (foundorgid !== undefined && foundorgid !== null && foundorgid.length === 36) { + orgId = foundorgid } } if (orgId.length === 0) { - toast( - "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.", - ); + toast("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.") return; } @@ -1569,7 +1597,6 @@ If you're interested, please let me know a time that works for you, or set up a responseJson.lead_info !== null ) { var leads = []; - console.log("LEADINFO: ", responseJson.lead_info) if (responseJson.lead_info.testing_shuffle) { leads.push("testing shuffle"); } @@ -1730,21 +1757,23 @@ If you're interested, please let me know a time that works for you, or set up a }) .then(function (responseJson) { if (responseJson.success === true) { - if ( - responseJson.region_url !== undefined && - responseJson.region_url !== null && - responseJson.region_url.length > 0 - ) { - localStorage.setItem("globalUrl", responseJson.region_url); + if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) { + localStorage.setItem("globalUrl", responseJson.region_url) //globalUrl = responseJson.region_url } setTimeout(() => { - window.location.reload(); + window.location.reload() }, 2000); toast("Successfully changed active organization - refreshing!"); } else { - toast("Failed changing org: " + responseJson.reason); + if (responseJson.reason !== undefined && responseJson.reason !== null) { + if (!responseJson.reason.includes("already")) { + toast("Failed changing org: " + responseJson.reason); + } + } else { + toast("Failed changing org") + } } }) .catch((error) => { @@ -1919,7 +1948,7 @@ If you're interested, please let me know a time that works for you, or set up a }; const rerunCloudWorkflows = (environment) => { - toast("Starting execution reruns. This can run in the background."); + toast("Starting execution reruns. This runs in the background. Check the /debug view to see the progress."); fetch(`${globalUrl}/api/v1/environments/${environment.id}/rerun`, { method: "GET", credentials: "include", @@ -2238,11 +2267,11 @@ If you're interested, please let me know a time that works for you, or set up a setEnvironments(responseJson); // Helper info for users in case they have a large queue and don't know about queue flushing - if ( - responseJson !== undefined && - responseJson !== null && - responseJson.length > 0 - ) { + if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { + if (responseJson.length === 1 && responseJson[0].Type !== "cloud") { + setListItemExpanded(0) + } + for (var i = 0; i < responseJson.length; i++) { const env = responseJson[i]; @@ -2351,8 +2380,8 @@ If you're interested, please let me know a time that works for you, or set up a 2: "app_auth", 3: "files", 4: "cache", - 5: "schedules", - 6: "environments", + 5: "triggers", + 6: "locations", 7: "suborgs", }; @@ -2446,14 +2475,9 @@ If you're interested, please let me know a time that works for you, or set up a } } - if ( - selectedOrganization.id === undefined && - userdata !== undefined && - userdata.active_org !== undefined && - orgRequest - ) { + if ( selectedOrganization.id === undefined && userdata !== undefined && userdata.active_org !== undefined && orgRequest) { setOrgRequest(false); - handleGetOrg(userdata.active_org.id); + handleGetOrg(userdata.active_org.id) } const paperStyle = { @@ -2802,10 +2826,11 @@ If you're interested, please let me know a time that works for you, or set up a }} PaperProps={{ style: { - backgroundColor: theme.palette.surfaceColor, + backgroundColor: theme.palette.platformColor, color: "white", minWidth: "800px", minHeight: "320px", + padding: 50, }, }} > @@ -3225,7 +3250,7 @@ If you're interested, please let me know a time that works for you, or set up a style={{ margin: 4, backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.3)", color: "white", minHeight: expanded ? 250 : "inherit", @@ -4516,16 +4541,6 @@ If you're interested, please let me know a time that works for you, or set up a style={{ minWidth: 350, maxWidth: 350 }} /> - - - - { - const elementName = "copy_element_shuffle"; - var copyText = - document.getElementById(elementName); - if ( - copyText !== null && - copyText !== undefined - ) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast( - "Can only copy over HTTPS (port 3443)", - ); - return; - } - - navigator.clipboard.writeText(data.apikey); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999, - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - toast("Apikey copied to clipboard"); - } - }} - > - - - - ) - } - /> - )} -
+

Webhooks

Webhooks used in Shuffle workflows.  @@ -5149,7 +5111,7 @@ If you're interested, please let me know a time that works for you, or set up a )} -
+

Pipelines

Controls a pipeline to run things.{" "} @@ -5172,8 +5134,8 @@ If you're interested, please let me know a time that works for you, or set up a }} /> {pipelines === undefined || - pipelines === null || - pipelines.length === 0 ? ( + pipelines === null || + pipelines.length === 0 ? (
+ + - {pipelines.map((pipeline, index) => { var bgColor = "#27292d"; @@ -5210,7 +5179,7 @@ If you're interested, please let me know a time that works for you, or set up a return ( - {pipeline.workflow_id} - - } + style={{ maxWidth: 150, minWidth: 150 }} + primary={pipeline.total_runs} /> - + + @@ -5461,7 +5423,7 @@ If you're interested, please let me know a time that works for you, or set up a src={data.app.large_image} style={{ maxWidth: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} /> style={{ minWidth: 75, maxWidth: 75 }} @@ -5728,7 +5690,7 @@ If you're interested, please let me know a time that works for you, or set up a -
+
Name @@ -5778,7 +5740,7 @@ If you're interested, please let me know a time that works for you, or set up a : - Environments failed to load. Please try again + Locations failed to load. Please try again }
@@ -5833,7 +5795,7 @@ If you're interested, please let me know a time that works for you, or set up a -
-

Environments

- - Decides what Orborus environment to run your workflow actions. If - you have scale problems, talk to our team: - support@shuffler.io.  - - Learn more - - -
- - - { - setShowArchived(!showArchived); - }} - />{" "} - Show disabled - - - - - - - - - - - - - - {environments === undefined || environments === null - ? null - : environments.map((environment, index) => { - if (!showArchived && environment.archived) { - return null; - } - if (environment.archived === undefined) { - return null; - } + const getOrborusCommand = (environment) => { + if (environment.Type === "cloud") { + //toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.",) + return + } - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } + if ( + props.userdata.active_org === undefined || + props.userdata.active_org === null + ) { + toast( + "No active organization yet. Are you logged in?", + ); + return; + } - // Check if there's a notification for it in userdata.priorities - var showCPUAlert = false; - var foundIndex = -1; - if ( - userdata !== undefined && - userdata !== null && - userdata.priorities !== undefined && - userdata.priorities !== null && - userdata.priorities.length > 0 - ) { - foundIndex = userdata.priorities.findIndex( - (prio) => prio.name.includes("CPU") && prio.active === true, - ); + const elementName = "copy_element_shuffle"; + const auth = + environment.auth === "" + ? "cb5st3d3Z!3X3zaJ*Pc" + : environment.auth; + const newUrl = + globalUrl === "https://shuffler.io" + ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" + : globalUrl; - if ( - foundIndex >= 0 && - userdata.priorities[foundIndex].name.endsWith( - environment.Name, - ) - ) { - showCPUAlert = true; - } - } + var skipPipeline = false + if (commandController.pipelines === true) { + skipPipeline = true + } - const queueSize = - environment.queue !== undefined && environment.queue !== null - ? environment.queue < 0 - ? 0 - : environment.queue > 1000 - ? ">1000" - : environment.queue - : 0; + var addProxy = false + if (commandController.proxies === true) { + addProxy = true + } - return ( - - - - - - ) : environment.run_type === "docker" ? ( - - - - ) : environment.run_type === "k8s" ? ( - - - - ) : ( - - - - ) - } - style={{ - minWidth: 50, - maxWidth: 50, - overflow: "hidden", - }} - /> - + if (installationTab === 1) { + return (`docker run -d \\ + --restart=always \\ + --name="shuffle-orborus" \\ + --pull=always \\ + --volume "/var/run/docker.sock:/var/run/docker.sock" \\ + -e AUTH="d85b017c-7f47-4d3a-bb20-9b00731bc397" \\ + -e ENVIRONMENT_NAME="swarm testing" \\ + -e ORG="9c4e7cd9-cfa4-457b-9ee5-3a9faa6e8c3c" \\ + -e SHUFFLE_WORKER_IMAGE="ghcr.io/shuffle/shuffle-worker:nightly" \\ + -e SHUFFLE_SWARM_CONFIG=run \\ + -e SHUFFLE_LOGS_DISABLED=true \\ + -e BASE_URL="${newUrl}" \\${addProxy ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -e SHUFFLE_SKIP_PIPELINES=true \\" : ""} + ghcr.io/shuffle/shuffle-orborus:latest + `) + } else if (installationTab === 2) { + return `https://shuffler.io/docs/configuration#kubernetes` + } + + const commandData = `docker rm shuffle-orborus --force; \\\ndocker run -d \\ + --restart=always \\ + --name="shuffle-orborus" \\ + --pull=always \\ + --volume "/var/run/docker.sock:/var/run/docker.sock" \\ + -e AUTH="${auth}" \\ + -e ENVIRONMENT_NAME="${environment.Name}" \\ + -e ORG="${props.userdata.active_org.id}" \\ + -e BASE_URL="${newUrl}" \\${addProxy ? "\n -e HTTPS_PROXY=IP:PORT \\" : ""}${skipPipeline ? "\n -e SHUFFLE_SKIP_PIPELINES=true \\" : ""} + ghcr.io/shuffle/shuffle-orborus:latest` + + return commandData + + + var copyText = + document.getElementById(elementName); + if ( + copyText !== null && + copyText !== undefined + ) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast( + "Can only copy over HTTPS (port 3443)", + ); + return; + } + + navigator.clipboard.writeText(commandData); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + toast("Orborus command copied to clipboard"); + } + } + +const environmentView = +curTab === 6 ? ( +
+
+

Locations

+ + Decides where to run your workflows and actions. Uses Shuffle's Orborus runner to handle queued jobs. Previously "Environments". + + If you have scale problems, talk to our team: support@shuffler.io.  + + Learn more + + +
+ + + { + setShowArchived(!showArchived); + }} + />{" "} + Show disabled + + + + + + + + + + + + + {environments === undefined || environments === null + ? null + : environments.map((environment, index) => { + if (!showArchived && environment.archived) { + return null; + } + + if (environment.archived === undefined) { + return null; + } + + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + // Check if there's a notification for it in userdata.priorities + var showCPUAlert = false; + var foundIndex = -1; + if ( + userdata !== undefined && + userdata !== null && + userdata.priorities !== undefined && + userdata.priorities !== null && + userdata.priorities.length > 0 + ) { + foundIndex = userdata.priorities.findIndex( + (prio) => prio.name.includes("CPU") && prio.active === true, + ); + + if ( + foundIndex >= 0 && + userdata.priorities[foundIndex].name.endsWith( + environment.Name, + ) + ) { + showCPUAlert = true; + } + } + + const queueSize = + environment.queue !== undefined && environment.queue !== null + ? environment.queue < 0 + ? 0 + : environment.queue > 1000 + ? ">1000" + : environment.queue + : 0; + + + const orborusCommandWrapper = () => { + // Check the current text + const orborusCommand = document.getElementById("orborus_command") + if (orborusCommand === undefined || orborusCommand === null) { + return getOrborusCommand(environment) + } + + return orborusCommand.textContent + } + + return ( + + { + if (environment.Type === "cloud") { + toast("Cloud environments are not configurable. To see what is possible, create a new environment.") + return + } + + setListItemExpanded(listItemExpanded === index ? -1 : index) + }} + > + + + + ) : environment.run_type === "docker" ? ( + + + + ) : environment.run_type === "k8s" ? ( + + + + ) : ( + + + + ) + } + style={{ + minWidth: 50, + maxWidth: 50, + overflow: "hidden", + }} + /> + ) : ( @@ -6410,7 +6485,7 @@ If you're interested, please let me know a time that works for you, or set up a placement="top" > @@ -6420,16 +6495,61 @@ If you're interested, please let me know a time that works for you, or set up a ) } style={{ - minWidth: 85, - maxWidth: 85, + minWidth: 60, + maxWidth: 60, + overflow: "hidden", + }} + /> + + + + : + environment?.data_lake?.enabled ? ( + + + + + + ) : ( + { + e.preventDefault() + e.stopPropagation() + + window.open("/detections/Sigma", "_blank") + }} + > + + + + + ) + } + style={{ + minWidth: 60, + maxWidth: 60, overflow: "hidden", }} /> @@ -6441,7 +6561,7 @@ If you're interested, please let me know a time that works for you, or set up a environment.running_ip.length === 0 ? (
Not running
) : ( - environment.running_ip + environment.running_ip.split(":")[0] ) ) : ( "N/A" @@ -6450,100 +6570,19 @@ If you're interested, please let me know a time that works for you, or set up a style={{ minWidth: 150, maxWidth: 150, + marginRight: 10, overflow: "hidden", }} /> - - { - if (environment.Type === "cloud") { - toast( - "No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.", - ); - return; - } - - if ( - props.userdata.active_org === undefined || - props.userdata.active_org === null - ) { - toast( - "No active organization yet. Are you logged in?", - ); - return; - } - - const elementName = "copy_element_shuffle"; - const auth = - environment.auth === "" - ? "cb5st3d3Z!3X3zaJ*Pc" - : environment.auth; - const newUrl = - globalUrl === "https://shuffler.io" - ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" - : globalUrl; - - const commandData = `docker run --restart=always --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${newUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest`; - var copyText = - document.getElementById(elementName); - if ( - copyText !== null && - copyText !== undefined - ) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast( - "Can only copy over HTTPS (port 3443)", - ); - return; - } - - navigator.clipboard.writeText(commandData); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999, - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - toast("Orborus command copied to clipboard"); - } - }} - > - - - - } - /> - - {environment.default ? null : ( - - )} - - +
+
+ + {listItemExpanded === index ? : } +
+ {listItemExpanded === index ? ( +
+
+ + Your Onprem Orborus instance + + + Orborus is the Shuffle queue handler that runs your hybrid workflows and manages pipelines. It can be run in Docker/k8s container on your server or in your cluster. Follow the steps below, and configure as need be. + + + { + setInstallationTab(inputValue) + }} + aria-label="disabled tabs example" + variant="scrollable" + scrollButtons="auto" + style={{textAlign: "center", marginTop: 25, }} + > + + Verbose (default) + + /> + + Scale + + /> + + k8s + + /> + + + {installationTab === 2 ? + + Check our Kubernetes documentation for more information on how to run Shuffle on Kubernetes. The status of the node will change when connected. + + : + + 1. Ensure Docker is installed and the target server can reach '{globalUrl}' + + } + + + + {installationTab === 2 ? null : + "2. Run this command on the server you want to run workflows or store Pipeline data on"} + + + {installationTab === 2 ? null : +
+
+ + {getOrborusCommand(environment)} + + +
+ + + Configure HTTP Proxies: { + if (commandController.proxies === undefined) { + commandController.proxies = true + } else { + commandController.proxies = !commandController.proxies + } + + setCommandController(commandController) + setUpdate(Math.random()) + }} + /> +
+ Disable Pipelines & Data Lake: { + if (commandController.pipelines === undefined) { + commandController.pipelines = true + } else { + commandController.pipelines = !commandController.pipelines + } + setCommandController(commandController) + setUpdate(Math.random()) + }} + /> +
+ } + + + {installationTab === 2 ? null : + + 3. Verify if the node is running. Try to refresh the page a little while after running the command. + + } + +
+
+ ) : null} + {showCPUAlert === false ? null : ( - Environments + Locations /> +
+   {modalView} {cloudSyncModal} {editUserModal} diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index d5146d66..54dd6a70 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -69,6 +69,7 @@ import { AvatarGroup, Autocomplete, Radio, + ButtonGroup, } from "@mui/material"; import { @@ -185,6 +186,20 @@ export const triggers = [ description: "Schedule time trigger", long_description: "Create a schedule based on cron", id: "", + }, + { + name: "Pipelines", + type: "TRIGGER", + status: "uninitialized", + description: "Run a pipeline trigger", + trigger_type: "PIPELINE", + errors: null, + is_valid: true, + label: "Pipeline", + environment: "onprem", + large_image: "/images/workflows/tenzir2.png", + long_description: "Controls a pipeline to run things", + id: "", }, { name: "Shuffle Workflow", @@ -215,20 +230,6 @@ export const triggers = [ long_description: "Take user input to continue execution", id: "", }, - { - name: "Pipelines", - type: "TRIGGER", - status: "uninitialized", - description: "Run a pipeline trigger", - trigger_type: "PIPELINE", - errors: null, - is_valid: true, - label: "Pipeline", - environment: "onprem", - large_image: "/images/workflows/tenzir2.png", - long_description: "Controls a pipeline to run things", - id: "", - }, ]; // Adds specific text to items @@ -404,7 +405,6 @@ const AngularWorkflow = (defaultprops) => { const [toolsApp, setToolsApp] = React.useState({}); const [currentView, setCurrentView] = React.useState(0); const [triggerAuthentication, setTriggerAuthentication] = React.useState({}); - const [triggerFolders, setTriggerFolders] = React.useState([]); const [workflows, setWorkflows] = React.useState([]); const [parentWorkflows, setParentWorkflows] = React.useState([]); const [showEnvironment, setShowEnvironment] = React.useState(false); @@ -458,6 +458,7 @@ const AngularWorkflow = (defaultprops) => { const [showSkippedActions, setShowSkippedActions] = React.useState(false); const [lastExecution, setLastExecution] = React.useState(""); const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false); + const [autoCompleting, setAutocompleting] = React.useState(false); const [authgroupModalOpen, setAuthgroupModalOpen] = React.useState(false); const [authGroups, setAuthGroups] = React.useState([]) @@ -529,6 +530,7 @@ const AngularWorkflow = (defaultprops) => { // eslint-disable-next-line no-unused-vars const [_, setUpdate] = useState(""); // Used to force rendring, don't remove + const [executionFilter, setExecutionFilter] = React.useState("ALL") const [workflowExecutions, setWorkflowExecutions] = React.useState([]); const [workflowExecutionCount, setWorkflowExecutionCount] = React.useState(0); const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0); @@ -543,6 +545,7 @@ const AngularWorkflow = (defaultprops) => { const [distributedFromParent, setDistributedFromParent] = React.useState("") const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) + const [allTriggers, setAllTriggers] = React.useState(undefined) const [suggestionBox, setSuggestionBox] = React.useState({ "position": { @@ -670,6 +673,8 @@ const releaseToConnectLabel = "Release to Connect" "field_number": -1, "actionlist": [], "field_id": "", + + "example": "", }) const [loadedApps, setLoadedApps] = React.useState([]) @@ -1033,6 +1038,7 @@ const releaseToConnectLabel = "Release to Connect" }; const getWorkflowExecutionCount = (workflowId) => { + fetch(`${globalUrl}/api/v1/workflows/${workflowId}/executions/count`, { method: "GET", headers: { @@ -1302,15 +1308,41 @@ const releaseToConnectLabel = "Release to Connect" }); }; - const getWorkflowExecution = (id, execution_id) => { - fetch(`${globalUrl}/api/v2/workflows/${id}/executions`, { - method: "GET", + const getWorkflowExecution = (id, execution_id, filter) => { + var url = `${globalUrl}/api/v2/workflows/${id}/executions` + var method = "GET" + if (filter === undefined || filter === null || filter.toUpperCase() === "ALL") { + + // Check for + if (executionFilter !== undefined && executionFilter !== null && executionFilter.length > 0) { + filter = executionFilter + } else { + filter = "ALL" + } + } + + var formattedBody = { + method: method, headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", - }) + } + + if (filter !== "ALL") { + formattedBody.method = "POST" + + formattedBody.body = JSON.stringify({ + "status": filter, + "workflow_id": id, + }) + + url = `${globalUrl}/api/v1/workflows/search` + + } + + fetch(url, formattedBody) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for WORKFLOW EXECUTION :O!"); @@ -1319,6 +1351,10 @@ const releaseToConnectLabel = "Release to Connect" return response.json(); }) .then((responseJson) => { + if (responseJson !== undefined && responseJson !== null && responseJson.runs !== undefined && responseJson.runs !== null) { + responseJson.executions = responseJson.runs + } + if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) { // - means it's opposite @@ -1383,8 +1419,6 @@ const releaseToConnectLabel = "Release to Connect" } else { const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var tmpView = new URLSearchParams(cursearch).get("execution_id"); - console.log("Alertnative execution id check: ", tmpView) - if (tmpView === undefined || tmpView === null || tmpView.length === 0) { const execution_id = tmpView; setExecutionModalView(1); @@ -1500,44 +1534,7 @@ const releaseToConnectLabel = "Release to Connect" if (selectedOption === "Kafka Queue") { trigger.parameters = [] - const topic = document.getElementById('topic')?.value - const bootstrapServers = document.getElementById('bootstrap_servers')?.value - const groupId = document.getElementById('group_id')?.value - const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; - - if(topic) { - trigger.parameters.push({ - name: "topic", - value: topic - }) - } else { - toast("Please enter the topic name"); - return; - } - - if (bootstrapServers) { - trigger.parameters.push({ - name: "bootstrap_servers", - value: bootstrapServers - }); - } else { - toast("please enter bootstrap server details"); - return; - } - - if (groupId) { - trigger.parameters.push({ - name: "group_id", - value: groupId - }); - } - - if (autoOffsetReset) { - trigger.parameters.push({ - name: "auto_offset_reset", - value: autoOffsetReset - }); - } + const pipeline = document.getElementById('pipeline')?.value setTenzirConfigModalOpen(false); } else if (selectedOption === "Syslog listener") { @@ -3457,9 +3454,7 @@ const releaseToConnectLabel = "Release to Connect" if (responseJson.childorg_workflow_ids !== undefined && responseJson.childorg_workflow_ids !== null && responseJson.childorg_workflow_ids.length > 0) { getChildWorkflows(responseJson.id) - } - - if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow.length > 0) { + } else if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow.length > 0) { getChildWorkflows(responseJson.parentorg_workflow) } @@ -3805,13 +3800,9 @@ const releaseToConnectLabel = "Release to Connect" setSelectedAction({}); setSelectedApp({}); setSelectedComment({}) - setSelectedEdge({}); - - setSelectedEdge({}) //setSelectedActionEnvironment({}) setTriggerAuthentication({}) - setTriggerFolders([]) setLocalFirstrequest(true) setSelectedTrigger({}); @@ -4036,13 +4027,9 @@ const releaseToConnectLabel = "Release to Connect" var found = false; for (let nodekey in allNodes) { const currentNode = allNodes[nodekey]; - if ( - currentNode.data.attachedTo === nodedata.id && - currentNode.data.isDescriptor - ) { - found = true; - console.log("FOUND THE NODE!"); - break; + if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) { + found = true + break } } @@ -4391,10 +4378,14 @@ const releaseToConnectLabel = "Release to Connect" // This is to find sample response and parse it as string var AppContext = [] + var originalParams = [] + if (inputAction !== undefined && inputAction !== null) { + // Reload the data without copying + inputAction = JSON.parse(JSON.stringify(inputAction)) + originalParams = JSON.parse(JSON.stringify(inputAction.parameters)) const parents = getParents(inputAction) - console.log("Parents: ", parents) var actionlist = [] if (parents.length > 1) { for (let [key,keyval] in Object.entries(parents)) { @@ -4473,9 +4464,26 @@ const releaseToConnectLabel = "Release to Connect" "action_name": item.action_name, "label": item.label, "example": exampledata, - "example_response": exampledata, + //"example_response": exampledata, }) } + + var params = [] + for (var paramkey in inputAction.parameters) { + const param = inputAction.parameters[paramkey] + if (param.configuration) { + continue + } + + // Mainly for booleans + if (param.options !== undefined && param.options !== null && param.options.length > 0) { + continue + } + + params.push(param) + } + + inputAction.parameters = params } var conversationData = { @@ -4486,6 +4494,7 @@ const releaseToConnectLabel = "Release to Connect" "workflow_id": workflow.id, } + if (inputAction !== undefined) { console.log("Add app context! This should them get parameters directly") conversationData.output_format = "action_parameters" @@ -4494,16 +4503,13 @@ const releaseToConnectLabel = "Release to Connect" conversationData.app_name = inputAction.app_name conversationData.action_name = inputAction.name conversationData.parameters = inputAction.parameters - - if (!value.includes(inputAction.label)) { - conversationData.query = inputAction.label.replaceAll("_", " ") - } } // Onprem not available yet (April 2023) // Should: Make OpenAI work for them with their own key - //fetch("https://shuffler.io/api/v1/conversation", { - fetch(`${globalUrl}/api/v1/conversation`, { + //fetch(`${globalUrl}/api/v1/conversation`, { + const url = `${globalUrl}/api/v1/conversation` + fetch(url, { method: "POST", headers: { "Content-Type": "application/json", @@ -4513,12 +4519,16 @@ const releaseToConnectLabel = "Release to Connect" credentials: "include", }) .then((response) => { + setAiQueryModalOpen(false) + setAutocompleting(false) if (setSuggestionLoading !== undefined) { setSuggestionLoading(false) } if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); + } else { + toast("Completion finished. Please verify your action!") } return response.json(); @@ -4543,52 +4553,71 @@ const releaseToConnectLabel = "Release to Connect" } var changed = false - - for (let paramkey in inputAction.parameters) { - const actionParam = inputAction.parameters[paramkey] - - if (actionParam.autocompleted === true) { + for (let respParamKey in responseJson.parameters) { + var respParam = responseJson.parameters[respParamKey] + if (respParam.value === undefined || respParam.value === null || respParam.value === "" ) { continue } - if (actionParam.configuration === true && actionParam.name !== "url") { - continue - } - - if (actionParam.value !== "" && actionParam.value !== actionParam.example) { - console.log("Skipping: ", actionParam) - continue - } - - for (let respParam of responseJson.parameters) { - if (respParam.name === actionParam.name) { - console.log("Found match for param: ", respParam) - - if (respParam.value === "") { - break - } - - changed = true - - inputAction.parameters[paramkey].autocompleted = true - inputAction.parameters[paramkey].value = respParam.value - break + for (let paramkey in originalParams) { + const actionParam = originalParams[paramkey] + /* + if (actionParam.value !== "" && actionParam.value !== actionParam.example) { + console.log("Skipping: ", actionParam) + continue } + */ + + if (respParam.name !== actionParam.name) { + continue + } + + const codeeditor = document.getElementById("shuffle-codeeditor") + if (codeeditor !== undefined && codeeditor !== null) { + const editorInstance = window?.ace?.edit("shuffle-codeeditor") + if (editorInstance === undefined || editorInstance === null) { + toast.error("Failed to find code editor instance") + return + } else { + editorInstance.setValue(respParam.value) + originalParams[paramkey].autocompleted = true + changed = true + } + } + + if (!changed) { + console.log("Found match for param: ", respParam) + changed = true + originalParams[paramkey].autocompleted = true + originalParams[paramkey].value = respParam.value + } + + break } } if (changed === true) { + inputAction.parameters = originalParams console.log("Setting action! Force update pls :)") setUpdate(Math.random()) + setSelectedAction(inputAction) + + // Find it in cytoscape and update the action + if (cy !== undefined && cy !== null) { + const cyAction = cy.getElementById(inputAction.id) + if (cyAction !== undefined && cyAction !== null) { + cyAction.data("parameters", inputAction.parameters) + } + } + } return } - console.log("Suggestionbox location: ", suggestionBox) - // Add action + console.log("Suggestionbox location: ", suggestionBox) if (responseJson.app_name !== undefined && responseJson.app_name !== null) { // Always added to 0, 0 // Should use suggestionBox.position.x, suggestionBox.position.y @@ -4640,6 +4669,8 @@ const releaseToConnectLabel = "Release to Connect" } }) .catch((error) => { + setAiQueryModalOpen(false) + setAutocompleting(false) if (setSuggestionLoading !== undefined) { setSuggestionLoading(false) } @@ -5278,9 +5309,6 @@ const releaseToConnectLabel = "Release to Connect" authenticationOptions[latestindex].last_modified = true } - - console.log("auth options 2: ", authenticationOptions) - curaction.authentication = authenticationOptions if ( curaction.selectedAuthentication === null || @@ -5290,7 +5318,6 @@ const releaseToConnectLabel = "Release to Connect" curaction.selectedAuthentication = {}; } } else { - console.log("auth option 3") curaction.authentication = [] curaction.authentication_id = ""; @@ -5374,7 +5401,7 @@ const releaseToConnectLabel = "Release to Connect" var trigger_index = workflow.triggers.findIndex( (a) => a.id === data.id - ); + ) if (trigger_index === -1) { workflow.triggers.push(data) @@ -5393,7 +5420,6 @@ const releaseToConnectLabel = "Release to Connect" } } else if (data.app_name === "Webhook") { if (workflow.triggers[trigger_index].parameters !== undefined && workflow.triggers[trigger_index].parameters !== null && workflow.triggers[trigger_index].parameters.length > 0) { - console.log("Can set params here!") workflow.triggers[trigger_index].parameters[0] = { name: "url", value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id, @@ -5481,6 +5507,34 @@ const releaseToConnectLabel = "Release to Connect" } } + if (allTriggers !== undefined && allTriggers !== null) { + + // Just checking all three. Could just make a new list, but meh + if (allTriggers.pipelines !== undefined && allTriggers.pipelines !== null) { + for (var pipelineKey in allTriggers.pipelines) { + if (allTriggers.pipelines[pipelineKey].id === data.id) { + data.status = allTriggers.pipelines[pipelineKey].status + } + } + } + + if (allTriggers.webhooks !== undefined && allTriggers.webhooks !== null) { + for (var webhookKey in allTriggers.webhooks) { + if (allTriggers.webhooks[webhookKey].id === data.id) { + data.status = allTriggers.webhooks[webhookKey].status + } + } + } + + if (allTriggers.schedules !== undefined && allTriggers.schedules !== null) { + for (var scheduleKey in allTriggers.schedules) { + if (allTriggers.schedules[scheduleKey].id === data.id) { + data.status = allTriggers.schedules[scheduleKey].status + } + } + } + } + setSelectedTriggerIndex(trigger_index) setSelectedTrigger(data) setSelectedActionEnvironment(data.env) @@ -7392,6 +7446,7 @@ const releaseToConnectLabel = "Release to Connect" console.log("NODE UNFINISHED (hover in): ", nodedata) // Should just be 1, so this should be fast enough :3 + /* const incomingEdges = event.target.incomers("edge").jsons() if (incomingEdges !== undefined && incomingEdges !== null) { for (var i = 0; i < incomingEdges.length; i++) { @@ -7410,6 +7465,7 @@ const releaseToConnectLabel = "Release to Connect" } return + */ } @@ -8315,13 +8371,39 @@ const releaseToConnectLabel = "Release to Connect" } setAllRevisions(responseJson) - setSelectedVersion(responseJson[0]) + setSelectedVersion(responseJson[0]) }) .catch((error) => { console.log("Error getting workflow revisions: ", error) }); } + const loadTriggers = () => { + const url = `${globalUrl}/api/v1/triggers` + fetch(url, + { + method: "GET", + headers: { "content-type": "application/json" }, + credentials: "include", + } + ) + .then((response) => { + if (response.status !== 200) { + throw new Error("No folders :o!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + setAllTriggers(responseJson) + } + }) + .catch((error) => { + console.log("Get outlook folders error: ", error.toString()); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps //useEffect(() => { if (firstrequest) { @@ -8329,6 +8411,7 @@ const releaseToConnectLabel = "Release to Connect" getWorkflow(props.match.params.key, {}); getChildWorkflows(props.match.params.key) getRevisionHistory(props.match.params.key) + loadTriggers() getApps() fetchUsecases() @@ -8374,8 +8457,6 @@ const releaseToConnectLabel = "Release to Connect" // 2nd load - configures cytoscape //} else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined && workflowRecommendations !== undefined) { } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) { - console.log("In POST graph setup!") - //This part has to load LAST, as it's kind of not async. //This means we need everything else to happen first. @@ -8383,8 +8464,6 @@ const releaseToConnectLabel = "Release to Connect" setEstablished(true); // Validate if the node is just a node lol - console.log("CY grid: ", cy.gridGuide) - // https://www.npmjs.com/package/cytoscape-grid-guide // if (cy.gridGuide !== undefined) { @@ -8441,7 +8520,6 @@ const releaseToConnectLabel = "Release to Connect" } // preview: true, - console.log("In POST graph setup 2") cy.fit(null, 200); cy.on("boxselect", "node", (e) => { @@ -8493,7 +8571,6 @@ const releaseToConnectLabel = "Release to Connect" document.title = "Workflow - " + workflow.name; - console.log("In POST graph setup 3") startWorkflowStream(props.match.params.key); registerKeys(); @@ -8551,7 +8628,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; @@ -8712,7 +8788,7 @@ const releaseToConnectLabel = "Release to Connect" }; const paperAppStyle = { - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, minHeight: isMobile ? 50 : 70, maxHeight: isMobile ? 50 : 70, minWidth: isMobile ? 50 : "100%", @@ -8725,7 +8801,7 @@ const releaseToConnectLabel = "Release to Connect" }; const paperVariableStyle = { - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, minHeight: 50, maxHeight: 50, minWidth: "100%", @@ -9114,27 +9190,19 @@ const releaseToConnectLabel = "Release to Connect"
{triggers.map((trigger, index) => { - /* - if (trigger.trigger_type === "PIPELINE") { - if (userdata.support !== true) { - return null - } - } - */ - // Hiding since March 2024 if (trigger.trigger_type === "EMAIL") { return null } const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 - var imageline = trigger.large_image.length === 0 ? + var imageline = trigger.large_image.length === 0 ? : { + if (env.archived) { + return false + } + + if (env.Name === "cloud" || env.Type === "cloud") { + return false + } + + return true + }) + + console.log("NEWENV: ", environments, newenv) + if (newenv !== undefined && newenv !== null) { + newAppData.environment = newenv.Name + } else { + newAppData.environment = "" + } + } + console.log("NEW DATA: ", newAppData) // Can all the data be in here? hmm const nodeToBeAdded = { @@ -9419,6 +9509,8 @@ const releaseToConnectLabel = "Release to Connect" const handleAppDrag = (e, app) => { const cycontainer = cy.container(); + console.log("APPDRAG!") + if (app.type === "TRIGGER") { handleTriggerDrag(e, app) @@ -9816,7 +9908,7 @@ const releaseToConnectLabel = "Release to Connect" pointerEvents: "none", userDrag: "none", userSelect: "none", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, height: isMobile ? 40 : 55, width: isMobile ? 40 : 55, }} @@ -9915,7 +10007,7 @@ const releaseToConnectLabel = "Release to Connect" }}> + const submitQueryModal = () => { + const changeActionTextfield = document.getElementById("change-action-textfield") + if (changeActionTextfield === undefined || changeActionTextfield === null) { + setAiQueryModalOpen(false) + toast.error("Failed to find textfield") + return + } + + if (changeActionTextfield.value === undefined || changeActionTextfield.value === null || changeActionTextfield.value === "") { + toast("Please provide how you want formatting to happen") + return + } + + setAutocompleting(true) + if (codeEditorModalOpen === true) { + autoFormatCodemodal(changeActionTextfield.value) + } else { + aiSubmit(changeActionTextfield.value, undefined, undefined, selectedAction) + } + } + + const autoFormatCodemodal = (input) => { + if (codeEditorModalOpen !== true) { + toast.error("Code editor is not open") + return + } + + if (editorData.name === undefined || editorData.name === null || editorData.name === "") { + toast.error("Failed to find editor field name") + return + } + + const codeeditor = document.getElementById("shuffle-codeeditor") + if (codeeditor === undefined || codeeditor === null) { + toast.error("Failed to find code editor html") + return + } + + const editorInstance = window?.ace?.edit("shuffle-codeeditor") + if (editorInstance === undefined || editorInstance === null) { + toast.error("Failed to find code editor instance") + return + } + + //console.log("ACE data: ", editorInstance.getValue()) + //editorInstance.setValue("HELO") + + // Should try to automatically fix this input + console.log("Running AI input fixer: ", selectedResult) + if (aiSubmit === undefined || selectedAction === undefined) { + toast.error("Failed to find AI submit function") + return + } + + // Should remove params from selectedAction that aren't parameterName + var tmpAction = JSON.parse(JSON.stringify(selectedAction)) + var tmpParams = tmpAction.parameters.filter((param) => param.name === editorData.name) + if (tmpParams.length !== 1) { + toast.error("Failed to find correct parameter in action") + return + } + + tmpParams[0].value = editorInstance.getValue() + tmpAction.parameters = tmpParams + aiSubmit(input, undefined, undefined, tmpAction) + } + const aiQueryModal = { + setAiQueryModalOpen(false) }} > - - Condition - + + { + }} + > + + + + { + setAiQueryModalOpen(false) + }} + > + + + + Shuffle AI + + + What you write here will be fed to the Shuffle AI to generate a change for the selected action or field. Best used for when you are stuck with formatting. Uses your AI credits (resets monthly). Alpha feature. Please give feedback to support@shuffler.io {"<"}3 + + + + + + ), + onKeyPress: (e) => { + if (e.key === "Enter" && !e.shiftKey) { + submitQueryModal() + } + }, + }} + + /> @@ -12159,659 +12392,6 @@ const releaseToConnectLabel = "Release to Connect" ); }; - // 1. GET the trigger authentication data - // 2. Parse the fields that are used (outlook & gmail) - // 3. Parse the folders that are selected - // 4. Start / stop - const EmailSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length === 0) { - return null; - } - - if (workflow.triggers[selectedTriggerIndex] === undefined) { - return null; - } - - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = [ - { value: "No folders selected yet", name: "outlookfolder" }, - ]; - selectedTrigger.parameters = [ - { value: "No folders selected yet", name: "outlookfolder" }, - ]; - setWorkflow(workflow); - setSelectedTrigger(selectedTrigger); - } - - const setGmailFolders = () => { - console.log("In set gmail folders"); - fetch( - globalUrl + - "/api/v1/triggers/gmail/getFolders?trigger_id=" + - selectedTrigger.id, - { - method: "GET", - headers: { "content-type": "application/json" }, - credentials: "include", - } - ) - .then((response) => { - if (response.status !== 200) { - throw new Error("No folders :o!"); - } - - return response.json(); - }) - .then((responseJson) => { - if ( - responseJson !== undefined && - responseJson !== null && - responseJson.success !== false && - responseJson.length > 0 - ) { - setTriggerFolders(responseJson); - } - - if ( - workflow.triggers[selectedTriggerIndex].parameters.length === 0 && - responseJson.length > 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = [ - { - value: responseJson[0].displayName, - name: "outlookfolder", - id: responseJson[0].id, - }, - ]; - selectedTrigger.parameters = [ - { - value: responseJson[0].displayName, - name: "outlookfolder", - id: responseJson[0].id, - }, - ]; - setWorkflow(workflow); - setSelectedTrigger(selectedTrigger); - } - }) - .catch((error) => { - console.log("Get gmail folder error: ", error.toString()); - }); - }; - - const setOutlookFolders = () => { - fetch( - globalUrl + - "/api/v1/triggers/outlook/getFolders?trigger_id=" + - selectedTrigger.id, - { - method: "GET", - headers: { "content-type": "application/json" }, - credentials: "include", - } - ) - .then((response) => { - if (response.status !== 200) { - throw new Error("No folders :o!"); - } - - return response.json(); - }) - .then((responseJson) => { - if ( - responseJson !== null && - responseJson.success !== false && - responseJson.length > 0 - ) { - console.log("Got trigger folders: ", triggerFolders) - setTriggerFolders(responseJson); - } - - if ( - workflow.triggers[selectedTriggerIndex].parameters.length === 0 && - responseJson.length > 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = [ - { - value: responseJson[0].displayName, - name: "outlookfolder", - id: responseJson[0].id, - }, - ] - selectedTrigger.parameters = [ - { - value: responseJson[0].displayName, - name: "outlookfolder", - id: responseJson[0].id, - }, - ]; - setWorkflow(workflow); - setSelectedTrigger(selectedTrigger); - } - }) - .catch((error) => { - console.log("Get outlook folders error: ", error.toString()); - }); - }; - - const getTriggerAuth = () => { - fetch(`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`, { - method: "GET", - headers: { "content-type": "application/json" }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - throw new Error("No trigger info :o!"); - } - - return response.json(); - }) - .then((responseJson) => { - - setTriggerAuthentication(responseJson); - }) - .catch((error) => { - //console.log(error.toString()); - console.log("Set trigger auth error: ", error.toString()); - }); - }; - - // Getting the triggers and the folders if they exist - if (localFirstrequest) { - //console.log("Trigger: ", selectedTrigger) - //console.log("Triggername: ", selectedTrigger.name) - //if (selectedTrigger.name.toLowerCase() === "gmail") { - setGmailFolders(); - setOutlookFolders(); - - getTriggerAuth(); - setLocalFirstrequest(false); - } - - const gmailButton = - selectedTrigger.name !== "Gmail" ? null : ( - - ); - - const outlookButton = - selectedTrigger.name !== "Office365" ? null : ( - - ); - - // FIXME - set everything in here to multifolder etc - var triggerInfo = "SET UP BUT NO TYPE :)"; - if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) { - // Should get the folders if they don't already exist - - if ( - triggerAuthentication.type === "outlook" || - triggerAuthentication.type === "gmail" - ) { - triggerInfo = ( -
- {selectedTrigger.status === "running" ? null : ( - -
-
-
- Change auth -
-
- {outlookButton} - {gmailButton} - - If you have trouble using this trigger, please contact us to get access - - - )} - - {triggerFolders === undefined || triggerFolders === null ? null : ( - -
-
-
- Select {triggerAuthentication.type === "gmail" ? "labels" : "a folder"} -
-
- {triggerFolders.length === 0 ? - - No folders found. Please authenticate first. If this persists, contact us. - - : - } - key={selectedTrigger} - > - {triggerFolders.map((folder) => { - var folderItem = ( - - ); - - if (folder.childFolderCount > 0) { - // Here to handle subfolders sometime later - folderItem = ( - - ); - } - - return folderItem; - })} - - } - - )} -
- ); - } else if (triggerAuthentication.type === "gmail") { - console.log("AUTH: ", triggerAuthentication); - triggerInfo = "SPECIAL GMAIL"; - } - } - - // Check - const argumentView = - Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? ( -
{triggerInfo}
- ) : ( -
-
-
-
- Login -
-
- {outlookButton} - {gmailButton} - - If you have trouble using this trigger, please { - if (window.drift !== undefined) { - window.drift.api.startInteraction({ interactionId: 340043 }) - } else { - console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) - } - }}>contact us to get access - -
- ); - - return ( -
-

- {selectedTrigger.app_name}: {selectedTrigger.status} -

- - What are email triggers? - - -
Name
- - {/* -
- Environment: - -
- */} - - {argumentView} -
-
- -
- - -
-
-
-
- ); - }; - const handleWorkflowSelectionUpdate = (e, isUserinput) => { if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { @@ -13380,7 +12960,7 @@ const releaseToConnectLabel = "Release to Connect" color: "white", height: 35, marginleft: 10, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, color: "rgba(255,255,255,0.4)", }} SelectDisplayProps={{ @@ -13428,7 +13008,7 @@ const releaseToConnectLabel = "Release to Connect" { setLastSaved(false) @@ -13613,7 +13193,7 @@ const releaseToConnectLabel = "Release to Connect" {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} + {data.name} : null} Choose Subflow '{data.name}' @@ -13627,7 +13207,7 @@ const releaseToConnectLabel = "Release to Connect" }} value={data} onClick={() => { - getWorkflowApps(data.id); + getWorkflowApps(data.id); handleWorkflowSelectionUpdate({ target: { value: data @@ -13646,7 +13226,7 @@ const releaseToConnectLabel = "Release to Connect" { setLastSaved(false) @@ -13752,7 +13332,7 @@ const releaseToConnectLabel = "Release to Connect" { // Workaround with event lol @@ -14609,7 +14189,7 @@ const releaseToConnectLabel = "Release to Connect" variant="body1" style={{ backgroundColor: theme.palette.inputColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} {...params} label="Find Associated App (optional)" @@ -14628,7 +14208,20 @@ const releaseToConnectLabel = "Release to Connect" MenuProps={{ disableScrollLock: true, }} - value={selectedTrigger.environment} + value={selectedTrigger.environment === undefined || selectedTrigger.environment === null || selectedTrigger.environment === "" ? + environments?.find((env) => { + if (env.archived) { + return false + } + + if (env.name === "cloud" || env.type === "cloud") { + return false + } + + + return true + }).name + : selectedTrigger.environment} disabled={selectedTrigger.status === "running"} SelectDisplayProps={{ style: { @@ -14663,7 +14256,7 @@ const releaseToConnectLabel = "Release to Connect" > {triggerEnvironments.map((data) => { if (data.archived) { - return null; + return null } return ( @@ -14713,7 +14306,7 @@ const releaseToConnectLabel = "Release to Connect" { @@ -14845,7 +14438,7 @@ const releaseToConnectLabel = "Release to Connect" { }} @@ -14898,7 +14491,7 @@ const releaseToConnectLabel = "Release to Connect" { }} @@ -15012,85 +14605,6 @@ const releaseToConnectLabel = "Release to Connect" }); }; - const startMailSub = (trigger, triggerindex) => { - var folders = []; - - if (triggerFolders === null || triggerFolders === undefined) { - return null; - } - - const splitItem = - workflow.triggers[selectedTriggerIndex].parameters[0].value.split( - splitter - ) - - console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem); - for (let splitkey in splitItem) { - const item = splitItem[splitkey]; - const curfolder = triggerFolders.find((a) => a.displayName === item); - if (curfolder === undefined) { - toast("Something went wrong with folder selection: " + item); - return; - } - - folders.push(curfolder.id); - } - - const data = { - name: trigger.name, - folders: folders, - id: trigger.id, - }; - - const requesttype = triggerAuthentication.type; - toast( - "Creating " + requesttype + " subscription with name " + trigger.name - ); - - fetch( - globalUrl + - "/api/v1/workflows/" + - props.match.params.key + - "/" + - requesttype, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(data), - credentials: "include", - } - ) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for stream results :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - toast("Failed to start trigger: " + responseJson.reason); - } else { - toast( - "Successfully started folder subscription trigger. Test it by sending yoursend an email" - ); - - workflow.triggers[triggerindex].status = "running"; - trigger.status = "running"; - setWorkflow(workflow); - setSelectedTrigger(trigger); - saveWorkflow(workflow); - } - }) - .catch((error) => { - //toast(error.toString()); - console.log("Start mailsub error: ", error.toString()); - }); - }; - const newWebhook = (trigger) => { const hookname = trigger.label; if (hookname.length === 0) { @@ -15282,7 +14796,7 @@ const releaseToConnectLabel = "Release to Connect" {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} + {data.name} : null} Choose Trigger '{data.name}' @@ -15502,7 +15016,7 @@ const releaseToConnectLabel = "Release to Connect"
- Parameters + What would you like to do? + {/*
{ @@ -15791,7 +15306,7 @@ const releaseToConnectLabel = "Release to Connect" }}} style={{ border: "1px solid rgba(255,255,255,0.3)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 10, cursor: "pointer", marginTop: 5, @@ -15804,9 +15319,8 @@ const releaseToConnectLabel = "Release to Connect" { - if (selectedTrigger.status !== "running"){ setSelectedOption("Syslog listener")}} - } + value={"Syslog listener"} name="option" /> @@ -15840,7 +15354,7 @@ const releaseToConnectLabel = "Release to Connect" }}} style={{ border: "1px solid rgba(255,255,255,0.3)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 10, cursor: "pointer", marginTop: 5, @@ -15854,7 +15368,9 @@ const releaseToConnectLabel = "Release to Connect" checked={selectedOption === "SigmaRule"} onChange={() => { if (selectedTrigger.status !== "running"){ - setSelectedOption("SigmaRule")}}} + setSelectedOption("SigmaRule") + } + }} value={"Sigma Rulesearch"} name="option" /> @@ -15863,19 +15379,22 @@ const releaseToConnectLabel = "Release to Connect" />
+ */} +
{ - if(selectedTrigger.status === "running"){ + if (selectedTrigger.status === "running"){ toast("please stop the trigger to edit the configuration"); return; } else { - setSelectedOption("Kafka Queue"); - setTenzirConfigModalOpen(true); - }}} + setSelectedOption("Kafka Queue"); + setTenzirConfigModalOpen(true); + } + }} style={{ border: "1px solid rgba(255,255,255,0.3)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 10, cursor: "pointer", marginTop: 5, @@ -15888,76 +15407,21 @@ const releaseToConnectLabel = "Release to Connect" { - if (selectedTrigger.status !== "running"){ - setSelectedOption("Kafka Queue")}}} + if (selectedTrigger.status !== "running") { + setSelectedOption("Kafka Queue") + + } + }} + value={"Kafka Queue"} name="option" /> } - label="Follow Kafka Queue" + label="Subscribe to a Kafka Queue" />
- - - - : null} - -
- + + + : null} +
); }; @@ -16937,7 +16426,7 @@ const releaseToConnectLabel = "Release to Connect" left: leftBarSize+20, color: "white", padding: 10, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} > @@ -17155,7 +16644,7 @@ const releaseToConnectLabel = "Release to Connect" height: 235, border: "1px solid #f85a3e", cursor: "pointer", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 10, backgroundColor: hovered ? theme.palette.surfaceColor : theme.palette.platformColor, }} @@ -17221,37 +16710,6 @@ const releaseToConnectLabel = "Release to Connect" cy.edges().remove() cy.nodes().remove() } - - // Remove all cytoscape triggers first? - /* - setTimeout(() => { - setupGraph(inputworkflow) - - cy.on("select", "node", (e) => { - onNodeSelect(e, appAuthentication); - }); - cy.on("select", "edge", (e) => onEdgeSelect(e)); - - cy.on("unselect", (e) => onUnselect(e)); - - cy.on("add", "node", (e) => onNodeAdded(e)); - cy.on("add", "edge", (e) => onEdgeAdded(e)); - cy.on("remove", "node", (e) => onNodeRemoved(e)); - cy.on("remove", "edge", (e) => onEdgeRemoved(e)); - - cy.on("mouseover", "edge", (e) => onEdgeHover(e)); - cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); - cy.on("mouseover", "node", (e) => onNodeHover(e)); - cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); - - // Handles dragging - cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); - cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); - - cy.on("cxttap", "node", (e) => onCtxTap(e)); - }, 25) - */ - } // Uses Org-Id referencing header to create a workflow while getting it in realtime @@ -17469,7 +16927,7 @@ const releaseToConnectLabel = "Release to Connect" @@ -18743,21 +18199,65 @@ const releaseToConnectLabel = "Release to Connect"
- + > + + + + + +
) : ( -
- - There are no executions yet, or they are not loaded. - - -
+ +
+ + No executions found for the '{executionFilter}' filter. + + + +
+
)}
) : ( @@ -19258,7 +18762,7 @@ const releaseToConnectLabel = "Release to Connect"
{ - window.open("/admin?tab=environments", "_blank") + window.open("/admin?tab=locations", "_blank") }}> {executionData.workflow.actions[0].environment} @@ -19434,7 +18938,7 @@ const releaseToConnectLabel = "Release to Connect" {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ? - No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Find out here. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io + No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Find out here. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
: null}
@@ -19450,7 +18954,7 @@ const releaseToConnectLabel = "Release to Connect" {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ? - No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io + No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io : null} @@ -19524,7 +19028,7 @@ const releaseToConnectLabel = "Release to Connect" if (triggers.length > 2) { if (data.action.app_name === "shuffle-subflow") { - const parsedImage = triggers[2].large_image; + const parsedImage = triggers[3].large_image; actionimg = ( {"ShuffleAction Logs - Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode. + Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
) @@ -19948,7 +19452,7 @@ const releaseToConnectLabel = "Release to Connect" cursor: "pointer", padding: 3, border: "1px solid rgba(255,255,255,0.3)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} onClick={() => { if (!showVariable) { @@ -20141,7 +19645,7 @@ const releaseToConnectLabel = "Release to Connect" } if (isCloud && stringjson.toLowerCase().includes("timeout error")) { - return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=environments to create an environment to connect to" + return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=locationsto create an environment to connect to" } if (stringjson.toLowerCase().includes("invalid header")) { @@ -20366,7 +19870,7 @@ const releaseToConnectLabel = "Release to Connect" {curapp === null ? null : ( {selectedResult.action.app_name} { rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ? -
- -
: null + +
+ +
+
+ : null } {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} @@ -21199,7 +20706,7 @@ const releaseToConnectLabel = "Release to Connect" @@ -21703,7 +21210,7 @@ const releaseToConnectLabel = "Release to Connect" style={{ backgroundColor: theme.palette.inputColor, padding: 15, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, marginBottom: 30, display: "flex", }} @@ -21803,10 +21310,7 @@ const releaseToConnectLabel = "Release to Connect" ) : null; - const TenzirConfigModal = () => { - if (!tenzirConfigModalOpen) return null; - - return ( + const tenzirConfigModal = !tenzirConfigModalOpen ? null : -
Configuration options for Kafka
+
Run a Tenzir Pipeline
+ + Runs a Tenzir pipeline. You can use the output of the pipeline in your workflow. +
- {selectedOption === "Kafka Queue" ? (
- Topic + Pipeline param.name === "topic", - )?.value || "" - } - /> - bootstrap.servers - param.name === "bootstrap_servers", - )?.value || "" - } - /> - group.id - param.name === "group_id", - )?.value || "" - } - /> - auto.offest.reset - param.name === "auto_offset_reset", - )?.value || "" - } + placeholder={""} + defaultValue={selectedOption} />
- ) : null}{" "}
@@ -21921,12 +21365,13 @@ const releaseToConnectLabel = "Release to Connect" onClick={() => { setTenzirConfigModalOpen(false); }} - color="primary" + color="secondary" > Cancel
- ); - }; - - const SuggestionBoxUi = () => { const [suggestionValue, setSuggestionValue] = useState(""); @@ -21951,7 +21392,7 @@ const releaseToConnectLabel = "Release to Connect" } return ( -
+
{/* */} @@ -22023,7 +21464,7 @@ const releaseToConnectLabel = "Release to Connect" }*/ const RevisionBox = (props) => { - const { revision, } = props + const { revision, showBorder, } = props if (revision === undefined || revision === null) { return null } @@ -22053,7 +21494,7 @@ const releaseToConnectLabel = "Release to Connect" return ( { if (newrevision.edited === selectedVersion.edited) { console.log("Same revision! No setting.") @@ -22146,12 +21587,14 @@ const releaseToConnectLabel = "Release to Connect" - + + +
{/*revision.edited === originalWorkflow.edited ? @@ -22228,44 +21671,54 @@ const releaseToConnectLabel = "Release to Connect" const drawerData = originalWorkflow !== undefined && originalWorkflow !== null ?
- - Version History - -
-
-
- - Current Version - - -
+ + Version History + + + Versions are stored for every change made, up to once per minute. When restoring a version, the changes will not take effect until you save the workflow. + + +
+ {/* +
+
+ + Current Version + + +
- -
+ +
+ */} {allRevisions.length > 0 ?
{ allRevisions.map((revision, index) => { + /* if(revision.edited === selectedVersion.edited){ return null } + */ return ( ) }) @@ -22440,8 +21893,10 @@ const releaseToConnectLabel = "Release to Connect" } } } else { - selectedAction.parameters[count].autocompleted = false - selectedAction.parameters[count].value = data + if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > count) { + selectedAction.parameters[count].autocompleted = false + selectedAction.parameters[count].value = data + } } setSelectedAction(selectedAction) @@ -22487,7 +21942,7 @@ const releaseToConnectLabel = "Release to Connect" const templatePopup = foundusecase.name === undefined || foundusecase.name === null || foundusecase.name === "" ? null : -
+
} + {tenzirConfigModal} {/*editWorkflowModal*/} {authgroupModal} {executionArgumentModal} @@ -22545,10 +22000,13 @@ const releaseToConnectLabel = "Release to Connect" fieldCount={editorData.field_number} actionlist={editorData.actionlist} fieldname={editorData.field_id} + editorData={editorData} changeActionParameterCodeMirror={changeActionParameterCodeMirror} - activeDialog={activeDialog} - setActiveDialog={setActiveDialog} + activeDialog={activeDialog} + setActiveDialog={setActiveDialog} + + setAiQueryModalOpen={setAiQueryModalOpen} /> : null} @@ -22565,7 +22023,7 @@ const releaseToConnectLabel = "Release to Connect" : null} {/*selectionOpen === true ? -
+
0 ? -
+
{ { { { setBaseUrl(tmpstring); }} /> -
+
Authentication diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 1b14fe20..ee9be748 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useContext, memo } from "react"; import { useInterval } from "react-powerhooks"; import theme from '../theme.jsx'; @@ -49,6 +49,7 @@ import { Folder as FolderIcon, LibraryBooks as LibraryBooksIcon, } from "@mui/icons-material"; +import { Context } from "../context/ContextApi.jsx"; import { ForkRight as ForkRightIcon, @@ -280,6 +281,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52 const Apps = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props; + //const [workflows, setWorkflows] = React.useState([]); const baseRepository = "https://github.com/frikky/shuffle-apps"; //const alert = useAlert(); @@ -423,7 +425,7 @@ const Apps = (props) => { minWidth: "100%", maxWidth: 612.5, marginBottom: 5, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, color: "white", backgroundColor: surfaceColor, cursor: "pointer", @@ -875,7 +877,7 @@ const Apps = (props) => { minWidth: viewWidth, maxWidth: viewWidth, color: "white", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, backgroundColor: surfaceColor, //display: "flex", marginBottom: 10, @@ -1551,7 +1553,7 @@ const Apps = (props) => { minHeight: 150, maxHeight: 150, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, } if (!makeFancy) { @@ -1829,7 +1831,7 @@ const Apps = (props) => { }}> {
{ minWidth: viewWidth, maxWidth: viewWidth, color: "white", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, //display: "flex", marginBottom: 10, overflow: "hidden", @@ -2373,6 +2375,7 @@ const Apps = (props) => { toast("Hotloading apps from location in .env"); setIsLoading(true); fetch(globalUrl + "/api/v1/apps/run_hotload", { + method: "POST", mode: "cors", headers: { Accept: "application/json", @@ -2999,7 +3002,7 @@ const Apps = (props) => { PaperProps={{ style: { backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, color: "white", minWidth: "800px", minHeight: "320px", @@ -3091,7 +3094,7 @@ const Apps = (props) => { PaperProps={{ style: { backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, color: "white", minWidth: "800px", minHeight: "320px", @@ -3202,22 +3205,50 @@ const Apps = (props) => { ) : null; - const loadedCheck = - isLoaded && !firstrequest ? ( -
- {appView} - {modalView} - {publishModal} - {generateAppView} - {appsModalLoad} - {deleteModal} -
- ) : ( -
- ); + const loadedCheck = isLoaded && !firstrequest ? ( + + + + ) : ( +
+ ); - // Maybe use gridview or something, idk return loadedCheck; }; export default Apps; + + +const AppsWrapper = memo(({ appView, modalView, userdata, publishModal, generateAppView, appsModalLoad, deleteModal }) => ( + <> + {appView} + {modalView} + {publishModal} + {generateAppView} + {appsModalLoad} + {deleteModal} + +)); + + +const SidebarAdjustWrapper = memo(({ userdata, children }) => { + + const {leftSideBarOpenByClick } = useContext(Context) + const marginLeft = userdata?.support + ? leftSideBarOpenByClick ? 250 : 80 + : 0; + + return ( +
+ {children} +
+ ); +}); diff --git a/frontend/src/views/DashboardViews.jsx b/frontend/src/views/DashboardViews.jsx index 57b761a3..b87bd142 100644 --- a/frontend/src/views/DashboardViews.jsx +++ b/frontend/src/views/DashboardViews.jsx @@ -197,7 +197,7 @@ const LineChartWrapper = ({keys, height, width}) => { const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No" return ( -
+
{name} @@ -281,7 +281,7 @@ const RadialChart = ({keys, setSelectedCategory}) => { }} content={(data, color) => { return ( -
+
{data.x} @@ -795,7 +795,7 @@ const Dashboard = (props) => { color: "white", height: 40, maxWidth: 150, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} > {data.available_keys.map((foundKey, index) => { diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index e05a7d90..b5046a7f 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useRef, useState } from "react" +import React, { useEffect, useLayoutEffect, useRef, useState, useContext, memo } from "react" import { toast } from 'react-toastify'; import Markdown from 'react-markdown' import theme from '../theme.jsx'; @@ -8,6 +8,7 @@ import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import remarkGfm from 'remark-gfm' +import { Context } from "../context/ContextApi.jsx"; import { Grid, TextField, @@ -166,7 +167,7 @@ export const Img = (props) => { return( {props.alt} @@ -213,7 +214,7 @@ export const CodeHandler = (props) => { backgroundColor: theme.palette.inputColor, overflowY: "auto", // Have it inline - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} > {validate.valid === true ? @@ -250,10 +251,8 @@ export const CodeHandler = (props) => { } const Docs = (defaultprops) => { - const { globalUrl, selectedDoc, serverside, serverMobile } = defaultprops; - + const { globalUrl, selectedDoc, serverside, serverMobile, userdata } = defaultprops; let navigate = useNavigate(); - // Quickfix for react router 5 -> 6 const params = useParams(); //var props = JSON.parse(JSON.stringify(defaultprops)) @@ -526,7 +525,7 @@ const Docs = (defaultprops) => { style={{ backgroundColor: theme.palette.inputColor, padding: 15, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, marginBottom: 30, display: "flex", }} @@ -892,7 +891,7 @@ const Docs = (defaultprops) => { target="_blank" style={{ textDecoration: "none", color: "inherit", flex: 1, margin: 10, }} > -
{ if (link === "" || link === undefined) { event.preventDefault() @@ -932,7 +931,7 @@ const Docs = (defaultprops) => { return ( -
{ setHover(true) }} @@ -968,8 +967,8 @@ const Docs = (defaultprops) => { Documentation
- /> - link="https://discord.gg/B2CBzUm" /> + /> + link="https://discord.gg/B2CBzUm" />
@@ -1265,15 +1264,44 @@ const Docs = (defaultprops) => {
); - // Padding and zIndex etc set because of footer in cloud. - const loadedCheck = ( -
- {postDataBrowser} - {postDataMobile} -
- ); + console.log("docs render") + + // Padding and zIndex etc set because of footer in cloud. +const loadedCheck = ( + + + +); + +return
{loadedCheck}
; - return
{loadedCheck}
; }; export default Docs; + +const DocsContent = memo(({postDataBrowser, postDataMobile}) => { + return( +
+ {postDataBrowser} + {postDataMobile} +
+)}) + +const DocsWrapper = memo(({userdata, children })=>{ + + const { leftSideBarOpenByClick, windowWidth } = useContext(Context); + + return ( +
+ {children} +
+ ) +}) diff --git a/frontend/src/views/GettingStarted.jsx b/frontend/src/views/GettingStarted.jsx index 39e59f2a..fc683f4a 100644 --- a/frontend/src/views/GettingStarted.jsx +++ b/frontend/src/views/GettingStarted.jsx @@ -2467,7 +2467,7 @@ const GettingStarted = (props) => { maxWidth: 1024, zIndex: 11, border: "1px solid rgba(255,255,255,0.1)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, textAlign: "center", overflow: "auto", }} diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index 9d741300..4b0f7389 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -43,6 +43,7 @@ import { Lock as LockIcon, LockOpen as LockOpenIcon, OpenInNew as OpenInNewIcon, + Edit as EditIcon, } from '@mui/icons-material'; const hrefStyle = { @@ -50,20 +51,13 @@ const hrefStyle = { textDecoration: "none" } -const bodyDivStyle = { - margin: "auto", - width: isMobile? "100%":"500px", - position: "relative", - - paddingBottom: 250, -} - const RunWorkflow = (defaultprops) => { const { globalUrl, userdata, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; let navigate = useNavigate(); const [_, setUpdate] = useState(""); // Used to force rendring, don't remove + const [explorerUi, setExplorerUi] = useState(false) const [message, setMessage] = useState(""); const [workflow, setWorkflow] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({}); @@ -80,6 +74,7 @@ const RunWorkflow = (defaultprops) => { const [sharingOpen, setSharingOpen] = React.useState(false) const [realtimeMarkdown, setRealtimeMarkdown] = React.useState("") const [forms, setForms] = React.useState([]) + const [boxWidth, setBoxWidth] = React.useState(500) const IframeWrapper = (props) => { var propsCopy = JSON.parse(JSON.stringify(props)) @@ -94,11 +89,21 @@ const RunWorkflow = (defaultprops) => { if (propsCopy.width === undefined || propsCopy.width === null) { propsCopy.width = 400 propsCopy.height = "auto" + + propsCopy.margin = "auto" } return Img(propsCopy) } + const bodyDivStyle = { + margin: "auto", + width: isMobile? "100%" : boxWidth, + position: "relative", + + paddingBottom: 250, + } + const boxStyle = { color: "white", padding: "25px 50px 50px 50px", @@ -112,7 +117,7 @@ const RunWorkflow = (defaultprops) => { props.match = {} props.match.params = params - const defaultTitle = workflow.name !== undefined ? workflow.name : "Run Workflow" + const defaultTitle = workflow.name !== undefined ? "Shuffle - Form for " + workflow.name : "Shuffle - Form to Run Workflows" if (document != undefined && document.title != defaultTitle) { document.title = defaultTitle } @@ -446,18 +451,6 @@ const RunWorkflow = (defaultprops) => { }) } - /* - useEffect(() => { - if (executionRequest === undefined || executionRequest === null || executionRequest.execution_id === undefined || executionRequest.execution_id === null || executionRequest.execution_id.length === 0) { - return - } - - if (executionRequest.start === true) { - start() - } - }, [executionRequest]) - */ - const { start, stop } = useInterval({ duration: 1500, startImmediate: true, @@ -520,8 +513,8 @@ const RunWorkflow = (defaultprops) => { if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0) { const newmarkdown = realtimeMarkdown.replace(`{{ ${workflow_id} }}`, "", -1) setRealtimeMarkdown(newmarkdown) - } else if (inputWorkflow.input_markdown !== undefined && inputWorkflow.input_markdown !== null && inputWorkflow.input_markdown.length > 0) { - const newmarkdown = inputWorkflow.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) + } else if (inputWorkflow.form_control.input_markdown !== undefined && inputWorkflow.form_control.input_markdown !== null && inputWorkflow.form_control.input_markdown.length > 0) { + const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) setRealtimeMarkdown(newmarkdown) } } @@ -531,10 +524,10 @@ const RunWorkflow = (defaultprops) => { console.log("Get workflow error: ", error.toString()) if (realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0) { - const newmarkdown = inputWorkflow.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) + const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) setRealtimeMarkdown(newmarkdown) - } else if (inputWorkflow.input_markdown !== undefined && inputWorkflow.input_markdown !== null && inputWorkflow.input_markdown.length > 0) { - const newmarkdown = inputWorkflow.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) + } else if (inputWorkflow.form_control.input_markdown !== undefined && inputWorkflow.form_control.input_markdown !== null && inputWorkflow.form_control.input_markdown.length > 0) { + const newmarkdown = inputWorkflow.form_control.input_markdown.replace(`{{ ${workflow_id} }}`, "", -1) setRealtimeMarkdown(newmarkdown) } }) @@ -642,10 +635,10 @@ const RunWorkflow = (defaultprops) => { } } - if (responseJson.input_markdown !== undefined && responseJson.input_markdown !== null && responseJson.input_markdown.length > 0) { + if (responseJson.form_control.input_markdown !== undefined && responseJson.form_control.input_markdown !== null && responseJson.form_control.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. - var newmarkdown = responseJson.input_markdown.replace("", "") + var newmarkdown = responseJson.form_control.input_markdown.replace("", "") const uuidRegex = /{{\s[a-f0-9-]+\s}}/g const found = newmarkdown.match(uuidRegex) @@ -697,7 +690,21 @@ const RunWorkflow = (defaultprops) => { handleExecutionLoader() handleGetOrg(responseJson.org_id) - setWorkflow(responseJson); + + if (responseJson.form_control === undefined || responseJson.form_control === null) { + responseJson.form_control = { + "input_markdown": "", + "output_yields": [], + "form_width": 500, + } + } + + + if (responseJson.form_control.form_width !== undefined && responseJson.form_control.form_width !== null && responseJson.form_control.form_width > 300) { + setBoxWidth(responseJson.form_control.form_width) + } + + setWorkflow(responseJson) }) .catch((error) => { console.log("Get workflow error: ", error.toString()); @@ -723,7 +730,6 @@ const RunWorkflow = (defaultprops) => { if (responseJson.result.startsWith("[") && responseJson.result.endsWith("]")) { try { responseJson.result = JSON.parse(responseJson.result).length - console.log("Set length to: ", responseJson.result) } catch (e) { console.log("Error parsing length: ", e) } @@ -875,8 +881,8 @@ const RunWorkflow = (defaultprops) => { const newmarkdown = realtimeMarkdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1) setRealtimeMarkdown(newmarkdown) - } else if (workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0) { - const newmarkdown = workflow.input_markdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1) + } else if (workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0) { + const newmarkdown = workflow.form_control.input_markdown.replace(`{{ ${responseJson.workflow.id} }}`, responseJson.result, -1) setRealtimeMarkdown(newmarkdown) } @@ -904,6 +910,17 @@ const RunWorkflow = (defaultprops) => { return } + if (props.match.params.key === undefined) { + setExplorerUi(true) + + if (isLoggedIn) { + loadForms(userdata.active_org.id) + handleGetOrg(userdata.active_org.id) + } + + return + } + getWorkflow(props.match.params.key, sourceNode) if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) { console.log("Get execution: ", execution_id) @@ -915,7 +932,6 @@ const RunWorkflow = (defaultprops) => { } }, [isLoaded]) - useEffect(() => { if (executionData === undefined || executionData === null || executionData === {}) { return @@ -995,20 +1011,69 @@ const RunWorkflow = (defaultprops) => { } } + const FormList = () => { + return ( +
+ {forms.map((form, formIndex) => { + if (form.id === undefined || form.id === null) { + return null + } + + return ( +
+ { + navigate(`/forms/${form.id}`) + getWorkflow(form.id, sourceNode) + setExplorerUi(false) + }} + currentWorkflowId={workflow.id} + /> +
+ ) + })} +
+ ) + } + + const ExplorerUi = () => { + return ( +
+ + {forms !== undefined && forms !== null && forms.length > 0 ? +
+ + Available forms + + +
+ : + + No Form Found + + } +
+ ) + } + var validResults = 0 const basedata =
- {workflow.id === undefined || workflow.id === null ? + {explorerUi === true ? + + : + workflow.id === undefined || workflow.id === null ?
- Loding Form Details... + Loading Form Details...
:
- {workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? + {workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0 ?
{ }} rehypePlugins={[rehypeRaw]} > - {realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0 ? realtimeMarkdown : workflow.input_markdown} + {realtimeMarkdown !== undefined && realtimeMarkdown !== null && realtimeMarkdown.length > 0 ? realtimeMarkdown : workflow.form_control.input_markdown}
: null}
{onSubmit(e)}} style={{margin: "25px 0px 15px 0px",}}> - {workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null : + {workflow.form_control.input_markdown !== undefined && workflow.form_control.input_markdown !== null && workflow.form_control.input_markdown.length > 0 ? null :
{workflow.name} {
@@ -1258,9 +1323,9 @@ const RunWorkflow = (defaultprops) => { } - {workflow.output_yields !== undefined && workflow.output_yields !== null && workflow.output_yields.length > 0 ? + {workflow.form_control.output_yields !== undefined && workflow.form_control.output_yields !== null && workflow.form_control.output_yields.length > 0 ?
- {workflow.output_yields.map((yieldItem, index) => { + {workflow.form_control.output_yields.map((yieldItem, index) => { if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) { return null } @@ -1329,7 +1394,7 @@ const RunWorkflow = (defaultprops) => { } - Forms are in Beta. Form submissions data includes your Organization's unique ID while logged in, or a unique identifier for your browser otherwise. Your input will be automatically sanitized. + Forms are in Beta. Form submission data includes your Organization's unique ID while logged in, or a unique identifier for your browser otherwise. Your input will be automatically sanitized.
@@ -1350,6 +1415,8 @@ const RunWorkflow = (defaultprops) => { expanded={true} setRealtimeMarkdown={setRealtimeMarkdown} + boxWidth={boxWidth} + setBoxWidth={setBoxWidth} scrollTo={"form_fill"} /> : null} @@ -1367,7 +1434,7 @@ const RunWorkflow = (defaultprops) => { minHeight: 400, maxHeight: 400, padding: 25, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, //minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, //maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550, }, @@ -1462,7 +1529,7 @@ const RunWorkflow = (defaultprops) => { setEditWorkflowModalOpen(true) }} > - Edit Details + Edit Form
: null} @@ -1476,7 +1543,8 @@ const RunWorkflow = (defaultprops) => { // Check width const overlap = window !== undefined && window.innerWidth !== undefined && window.innerWidth < 1300 - const formSidebar = !isLoaded || overlap || !(forms !== undefined && forms !== null && forms.length > 1) ? null : + + const formSidebar = explorerUi === true || !isLoaded || overlap || !(forms !== undefined && forms !== null && forms.length > 1) ? null :
{ minHeight: 500, maxHeight: 500, position: "absolute", - left: 100, + left: 150, top: 0, border: "1px solid rgba(255,255,255,0.3)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 25, @@ -1497,7 +1565,7 @@ const RunWorkflow = (defaultprops) => { }}> {selectedOrganization !== undefined && selectedOrganization !== null ?
- + {selectedOrganization.name} @@ -1506,41 +1574,7 @@ const RunWorkflow = (defaultprops) => { : null} {forms !== undefined && forms !== null && forms.length > 0 ? -
- {forms.map((form, formIndex) => { - if (form.id === undefined || form.id === null) { - return null - } - - return ( -
- { - navigate(`/forms/${form.id}`) - getWorkflow(form.id, sourceNode) - }} - currentWorkflowId={workflow.id} - /> - - {/* - - */} -
- ) - })} -
+ :
No forms loaded @@ -1557,4 +1591,4 @@ const RunWorkflow = (defaultprops) => { ) } -export default RunWorkflow; +export default RunWorkflow diff --git a/frontend/src/views/Search.jsx b/frontend/src/views/Search.jsx index fa9a2268..9ebd79db 100644 --- a/frontend/src/views/Search.jsx +++ b/frontend/src/views/Search.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect, useMemo, useContext } from "react"; import theme from "../theme.jsx"; import { isMobile } from "react-device-detect"; @@ -11,6 +11,7 @@ import { Tabs, Tab, setRef } from "@mui/material"; import { styled } from "@mui/material/styles"; import { makeStyles } from '@mui/styles'; import DiscordChat from "../components/DiscordChat.jsx"; +import { Context } from "../context/ContextApi.jsx"; import { Apps as AppsIcon, @@ -21,16 +22,14 @@ import { DescriptionOutlined as DescriptionOutlinedIcon, } from "@mui/icons-material"; -import { - Typography -} from "@mui/material" - +import {Typography} from "@mui/material"; // Should be different if logged in :| const Search = (props) => { const { globalUrl, isLoaded, serverside, userdata, hidemargins, isHeader } = props; let navigate = useNavigate(); + const { leftSideBarOpenByClick } = useContext(Context); const [curTab, setCurTab] = useState(0); const iconStyle = { marginRight: isHeader ? null : 10 }; @@ -124,14 +123,15 @@ const Search = (props) => { flex: "1", marginLeft: isHeader ? null : 10, marginRight: isHeader ? null : 10, - paddingLeft: isHeader ? null : 30, paddingRight: isHeader ? null : 30, paddingBottom: isHeader ? null : 30, paddingTop: hidemargins === true ? 0 : isHeader ? null : 30, display: "flex", flexDirection: "column", overflowX: "hidden", - minHeight: 400 + minHeight: 400, + paddingLeft: leftSideBarOpenByClick ? 250 : 0, + transition: "padding-left 0.3s ease", }; const views = { @@ -223,7 +223,7 @@ const Search = (props) => { margin: isHeader ? null : "auto", marginTop: hidemargins === true ? 0 : isHeader ? null : 25, backgroundColor: "rgba(33, 33, 33, 1)", - borderRadius: 8 + borderRadius: 8, }} value={curTab} indicatorColor="primary" diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index cbcda6dd..bb99e037 100755 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -302,7 +302,7 @@ const SetAuthentication = (props) => { } return ( -
+
{ display: "flex", flexDirection: "column", padding: "0px 0px 12px 0px", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }; const currentOwner = checkOwner(data, userdata); @@ -636,7 +636,7 @@ const Settings = (props) => { } return ( - + { ); const landingpageData = ( -
+
{imageInfo}

Settings

@@ -1065,7 +1065,7 @@ const Settings = (props) => { { { navigate(`/usecases?selected_object=${fixedName}`) } }}> - { + { }}> {!selectedItem ?
@@ -799,7 +799,7 @@ const UsecaseListComponent = (props) => { style={{ backgroundColor: theme.palette.inputColor, height: 50, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, }} onChange={(event, newValue) => { //handleWorkflowSelectionUpdate({ target: { value: newValue} }) @@ -856,7 +856,7 @@ const UsecaseListComponent = (props) => { {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {newname} + {newname} : null} Choose {newname} @@ -881,7 +881,7 @@ const UsecaseListComponent = (props) => { { target="_blank" style={{ textDecoration: "none", color: "rgba(255,255,255,0.7)", marginRight: 5, }} > -
- +
+ {subdata.name} @@ -983,7 +983,7 @@ const UsecaseListComponent = (props) => { height: 350, width: isMobile? null:350, marginTop: isMobile ? 50:null, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, border: "1px solid rgba(255,255,255,0.3)", padding: 5, backgroundColor: theme.palette.backgroundColor, @@ -1113,7 +1113,7 @@ const RadialChart = ({keys, setSelectedCategory}) => { }} content={(data, color) => { return ( -
+
{data.x} diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx index 8c57c3ff..7dd21dea 100644 --- a/frontend/src/views/Welcome.jsx +++ b/frontend/src/views/Welcome.jsx @@ -304,14 +304,14 @@ const Welcome = (props) => { minWidth: isMobile ? null : 275, backgroundColor: theme.palette.surfaceColor, color: "white", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, } const actionObject = { padding: "25px", maxHeight: 280, minHeight: 280, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, } const imageStyle = { @@ -340,7 +340,7 @@ const Welcome = (props) => { const defaultImage = "/images/experienced.png" const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage return ( -
+
{/*
@@ -354,7 +354,7 @@ const Welcome = (props) => { color="primary" style={{ backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, padding: 12, border: "1px solid rgba(255,255,255,0.3)", maxWidth: 500, @@ -434,7 +434,7 @@ const Welcome = (props) => { We will use this information to personalize your automation */}
-
+
{ if (isCloud) { ReactGA.event({ @@ -478,7 +478,7 @@ const Welcome = (props) => { navigate("/workflows?message=Skipped intro") }}> - + Experienced diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 00eca27b..6539b0c1 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useContext } from "react"; +import React, { useEffect, useContext, memo } from "react"; import ReactDOM from "react-dom" import { makeStyles } from "@mui/styles"; @@ -6,7 +6,7 @@ import { Navigate } from "react-router-dom"; import SecurityFramework from '../components/SecurityFramework.jsx'; import EditWorkflow from "../components/EditWorkflow.jsx" import Priority from "../components/Priority.jsx"; - +import { Context } from "../context/ContextApi.jsx"; import { isMobile } from "react-device-detect" import { @@ -92,37 +92,40 @@ import theme from "../theme.jsx"; const svgSize = 24; const imagesize = 22; -const useStyles = makeStyles((theme) => ({ - datagrid: { - border: 0, - "& .MuiDataGrid-columnsContainer": { - backgroundColor: - theme.palette.type === "light" ? "#fafafa" : theme.palette.inputColor, - }, - "& .MuiDataGrid-iconSeparator": { - display: "none", - }, - "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { - borderRight: `1px solid ${ - theme.palette.type === "light" ? "white" : "#303030" - }`, - }, - "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { - borderBottom: `1px solid ${ - theme.palette.type === "light" ? "#f0f0f0" : "#303030" - }`, - }, - "& .MuiDataGrid-cell": { - color: - theme.palette.type === "light" ? "white" : "rgba(255,255,255,0.65)", - }, - "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": - { - borderRadius: 0, - color: "white", - }, - }, -})); +const useStyles = makeStyles(() => { + + return { + datagrid: { + border: 0, + "& .MuiDataGrid-columnsContainer": { + backgroundColor: + theme?.palette?.type === "light" ? "#fafafa" : theme?.palette?.inputColor, + }, + "& .MuiDataGrid-iconSeparator": { + display: "none", + }, + "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { + borderRight: `1px solid ${ + theme?.palette?.type === "light" ? "white" : "#303030" + }`, + }, + "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { + borderBottom: `1px solid ${ + theme?.palette?.type === "light" ? "#f0f0f0" : "#303030" + }`, + }, + "& .MuiDataGrid-cell": { + color: + theme?.palette?.type === "light" ? "white" : "rgba(255,255,255,0.65)", + }, + "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": + { + borderRadius: 0, + color: "white", + }, + }, + } +}) // Takes an action in Shuffle and // Returns information about the icon, the color etc to be used @@ -481,9 +484,9 @@ export const validateJson = (showResult) => { }; } } catch (e) { - showResult = showResult.split("'").join('"'); try { + showResult = showResult.split("'").join('"'); if (!showResult.includes("{") && !showResult.includes("[")) { jsonvalid = false; } @@ -590,13 +593,37 @@ export const validateJson = (showResult) => { }; }; +//Custom hook for handling styling of the dropzone +const useDropzoneStyles = () => { + const { leftSideBarOpenByClick } = useContext(Context); + + return { + maxWidth: window.innerWidth > 1366 ? 1366 : isMobile ? "100%" : 1200, + margin: "auto", + padding: 20, + paddingLeft: leftSideBarOpenByClick ? 200 : 0, + transition: "padding-left 0.3s ease", + }; +}; + +//Wrapper for the dropzone component +const DropzoneWrapper = memo(({ onDrop, WorkflowView }) => { + const dropzoneStyles = useDropzoneStyles(); + return ( + + + + ); +}); + + const Workflows = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata, checkLogin } = props; document.title = "Shuffle - Workflows"; let navigate = useNavigate(); - const classes = useStyles(theme); + const classes = useStyles(theme) const imgSize = 60; const referenceUrl = globalUrl + "/api/v1/hooks/"; @@ -702,7 +729,7 @@ const Workflows = (props) => { console.log("Using filters: ", filters) if (filters.length === 0) { setFilteredWorkflows(workflows); - handleKeysetting(allUsecases, workflows) + handleKeysetting(allUsecases, workflows) return; } @@ -778,9 +805,9 @@ const Workflows = (props) => { } } - console.log("Changing workflow filter, and finding new usecase mappings!") + console.log("Changing workflow filter, and finding new usecase mappings!") if (newWorkflows.length !== workflows.length) { - handleKeysetting(allUsecases, newWorkflows) + handleKeysetting(allUsecases, newWorkflows) setFilteredWorkflows(newWorkflows); } @@ -1194,8 +1221,8 @@ const Workflows = (props) => { return response.json(); }) .then((responseJson) => { - if (responseJson !== undefined) { + if (responseJson !== undefined) { var newarray = [] for (var wfkey in responseJson) { const wf = responseJson[wfkey] @@ -1228,12 +1255,10 @@ const Workflows = (props) => { } } - if (newarray.length > 0) { - try { - localStorage.setItem("workflows", JSON.stringify(newarray)) - } catch (e) { - console.log("Failed to set workflows in localstorage: ", e) - } + try { + localStorage.setItem("workflows", JSON.stringify(newarray)) + } catch (e) { + console.log("Failed to set workflows in localstorage: ", e) } // Ensures the zooming happens only once per load @@ -1285,7 +1310,41 @@ const Workflows = (props) => { .catch((error) => { toast(error.toString()); }); - }; + } + + const findMatches = (category, workflows) => { + category.matches = [] + for (var subcategorykey in category.list) { + var subcategory = category.list[subcategorykey] + subcategory.matches = [] + + for (var workflowkey in workflows) { + const workflow = workflows[workflowkey] + + if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { + for (var usecasekey in workflow.usecase_ids) { + + if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { + //console.log("Got match: ", workflow.usecase_ids[usecasekey]) + + category.matches.push({ + "workflow": workflow.id, + "category": subcategory.name, + }) + subcategory.matches.push(workflow.id) + break + } + } + } + + if (subcategory.matches.length > 0) { + break + } + } + } + + return category + } const handleKeysetting = (categorydata, workflows) => { if (workflows !== undefined && workflows !== null) { @@ -1297,37 +1356,7 @@ const Workflows = (props) => { continue } - category.matches = [] - - for (var subcategorykey in category.list) { - var subcategory = category.list[subcategorykey] - subcategory.matches = [] - - for (var workflowkey in workflows) { - const workflow = workflows[workflowkey] - - if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { - for (var usecasekey in workflow.usecase_ids) { - - if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { - //console.log("Got match: ", workflow.usecase_ids[usecasekey]) - - category.matches.push({ - "workflow": workflow.id, - "category": subcategory.name, - }) - subcategory.matches.push(workflow.id) - break - } - } - } - - if (subcategory.matches.length > 0) { - break - } - } - } - + category = findMatches(category, workflows) newcategories.push(category) } @@ -1440,7 +1469,7 @@ const Workflows = (props) => { display: "flex", boxSizing: "border-box", position: "relative", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, backgroundColor: theme.palette.surfaceColor, }; @@ -2229,7 +2258,7 @@ const Workflows = (props) => { } return ( -
+
{selectedCategory !== "" ? @@ -2277,7 +2306,7 @@ const Workflows = (props) => { title={
{data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} + {data.name} : null} Edit '{data.name}' @@ -2493,7 +2522,7 @@ const Workflows = (props) => { {workflowMenuButtons}
) : null} - {(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data.input_markdown !== undefined && data.input_markdown !== null && data.input_markdown !== "") ? + {(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data?.form_control?.input_markdown !== undefined && data?.form_control?.input_markdown !== null && data?.form_control?.input_markdown !== "") ?
{ autoHeight density="standard" onSelectionModelChange={(newSelection) => { - //setSelectedWorkflows(newSelection.selectionModel); - console.log(newSelection) - setSelectedWorkflowIndexes(newSelection) }} selectionModel={selectedWorkflowIndexes} @@ -3210,7 +3236,6 @@ const Workflows = (props) => { None {usecases.map((usecase, index) => { - //console.log(usecase) return ( { // ); // } - const WorkflowView = () => { + const WorkflowView = memo(() => { if (workflows.length === 0) { } @@ -3660,9 +3685,14 @@ const Workflows = (props) => { {!isMobile && !hasWorkflows && usecases !== null && usecases !== undefined && usecases.length > 0 ?
{usecases.map((usecase, index) => { - //console.log(usecase) + if (usecase.name === "5. Verify") { + return null + } + const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length/usecase.list.length*100) : 0 - //console.log("Usecase Matches: ", usecase.matches, ", Percent: ", percentDone) + if (percentDone === 0) { + usecase = findMatches(usecase, workflows) + } return ( { flex: 1, backgroundImage: `linear-gradient(to right, ${usecase.color}, ${usecase.color} ${percentDone}%, transparent ${percentDone}%, transparent 100%)`, backgroundColor: filters.includes(usecase.name.toLowerCase()) ? null : theme.palette.surfaceColor, - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, marginRight: index === usecases.length-1 ? 0 : 10, cursor: "pointer", border: `2px solid ${usecase.color}`, @@ -3692,7 +3722,7 @@ const Workflows = (props) => { {usecase.name} - + {usecase.matches.length}/{usecase.list.length} @@ -3715,7 +3745,7 @@ const Workflows = (props) => { minWidth: isMobile ? "100%" : 1024, zIndex: 11, border: "1px solid rgba(255,255,255,0.1)", - borderRadius: theme.palette.borderRadius, + borderRadius: theme.palette?.borderRadius, textAlign: "center", overflow: "auto", }} @@ -3734,7 +3764,6 @@ const Workflows = (props) => { } if (data.app_name.toLowerCase() === "integration framework") { - console.log("Skipping: ", data.app_name) return null } @@ -3813,7 +3842,7 @@ const Workflows = (props) => { ) : null} {userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && userdata.priorities[0].name.includes("CPU") && userdata.priorities[0].active === true ? -
@@ -3905,7 +3934,7 @@ const Workflows = (props) => {
); - }; + }); const importWorkflowsFromUrl = (url) => { console.log("IMPORT WORKFLOWS FROM ", downloadUrl); @@ -4173,7 +4202,7 @@ const Workflows = (props) => { Setup progress: {isNaN(percentDone) ? 0 : percentDone}% - + Follow these steps to get you up and running! @@ -4251,17 +4280,7 @@ const Workflows = (props) => { */} - 1366 ? 1366 : isMobile ? "100%" : 1200, - margin: "auto", - padding: 20, - paddingLeft: userdata?.support ? 80 : 0 - }} - onDrop={uploadFile} - > - - + {/*modalView*/} {deleteModal} {exportVerifyModal} @@ -4269,7 +4288,7 @@ const Workflows = (props) => { {workflowDownloadModalOpen} {/*!drawerOpen ? -
+
{ setDrawerOpen(true)